學習JUC原始碼(2)——自定義同步元件

JJian發表於2020-12-08

前言

  在之前的博文(學習JUC原始碼(1)——AQS同步佇列(原始碼分析結合圖文理解))中,已經介紹了AQS同步佇列的相關原理與概念,這裡為了再加深理解ReentranLock等原始碼,模仿構造同步元件的基本模式,編寫不可重複的互斥鎖Mutex與指定共享執行緒數量的共享鎖。MySharedLock。

  主要參考資料《Java併發程式設計藝術》(有需要的小夥伴可以找我,我這裡只有電子PDF)同時結合ReentranLock、AQS等原始碼。

 


 

一、構造同步元件的模式

叢概念方層面,在中,我們知道鎖與同步器的相關概念:

  • 同步器是實現鎖的關鍵,在鎖的實現中聚合同步器,利用同步器實現鎖的語義;
  • 鎖是面向使用者的,提供鎖互動的實現;
  • 同步器是面向鎖的實現者,簡化了鎖的實現方式,遮蔽了同步狀態管理、執行緒排隊、等待/喚醒等底層操作。

從程式碼層面,同步器是基於模板模式實現的,可以通過可重寫的方法中的隨便一個窺探:

  /**
     * 模板方法:
     *  protected關鍵字
     *  沒有任何實現
     * @param arg
     * @return
     */
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

也就是需要進行以下幾步:

1)繼承同步器重寫指定方法(idea中extends AQS點選快捷鍵ctrl+O即可顯示)

  • tryAcquire(int arg):獨佔式獲取同步狀態;
  • tryRelease(int arg):獨佔式釋放同步狀態;
  • tryAcquireShared(int arg):共享式獲取同步狀態,返回大於0的值表示獲取成功,否則失敗
  • tryReleaseShared(int arg):共享式釋放鎖
  • isHeldExclusively():當前執行緒是否在獨佔模式下被執行緒佔用,一般該方法表示是否被當前執行緒佔用

2)隨後將同步器組合在自定義同步元件的實現中,即定義內部類Syn繼承AQS,在Syn中重寫AQS方法:

public class Sync extends AbstractQueuedSynchronizer{
        @Override
        protected boolean tryAcquire(int arg) {
            final Thread current = Thread.currentThread();
            if (compareAndSetState(0, 1)) {
                // 獲取成功之後,當前執行緒是該鎖的持有者,不需要再可重入數
                setExclusiveOwnerThread(current);
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
              return getState() == 1;
        }
        // 返回Condition,每個Condition都包含了一個佇列
        Condition newCondition() {
            return new ConditionObject();
        }
    }

3)最後呼叫同步器提供的模板方法,即同步元件類實現Lock方法之後,在lock/unlock方法中呼叫內部類Syn的方法acquire(int arg)等方法

public class Mutex implements Lock {
    
   ........
    @Override
    public void lock() {
        sync.acquire(1);
    }
    @Override
    public void unlock() {
        sync.release(1);
    }
    ........

}

具體請看下面的實踐部分

二、互斥不可重入鎖

 在我之前寫過的博文中(詳解Java鎖的升級與對比(1)——鎖的分類與細節(結合部分原始碼))介紹可重入鎖與不可重入鎖的區別時,就寫到JUC中沒有不可重入鎖的具體實現,但是可以類比,現在呢,我們可以做到實現了,具體看下面程式碼,模式完全符合依賴Lock與AQS構造同步元件模式。

(1)Mutex程式碼實現(核心關鍵實現已經在程式碼中註釋)

public class Mutex implements Lock {

    private final Sync sync = new Sync();
    public class Sync extends AbstractQueuedSynchronizer{
        @Override
        protected boolean tryAcquire(int arg) {
            final Thread current = Thread.currentThread();
            if (compareAndSetState(0, 1)) {
                // 獲取成功之後,當前執行緒是該鎖的持有者,不需要再可重入數
                setExclusiveOwnerThread(current);
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
              return getState() == 1;
        }
        // 返回Condition,每個Condition都包含了一個佇列
        Condition newCondition() {
            return new ConditionObject();
        }
    }


    @Override
    public void lock() {
        sync.acquire(1);
    }
    @Override
    public void unlock() {
        sync.release(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }



    @Override
    public Condition newCondition() {
        return null;
    }
}

其中核心程式碼就是重寫的兩個方法:

  • tryAcquire(int arg)方法:主要是設定同獨佔式更新同步狀態,CAS實現state+1
  • tryRelease(int arg)方法:獨佔式釋放同步狀態,釋放鎖持有 

(2)測試Demo

public class MutexDemo {

    @Test
    public void test(){
        final Mutex lock = new Mutex();
        class Worker extends Thread {
            @Override
            public void run() {
                // 一直不停在獲取鎖
                while (true) {
                    lock.lock();
                    try {
                        System.out.println(Thread.currentThread().getName() +" hold lock, "+new Date());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        lock.unlock();
                        System.out.println(Thread.currentThread().getName() +" release lock, "+new Date());
                    }
                }
            }

        }
        for (int i = 0; i < 10; i++) {
            Worker worker = new Worker();
            // 以守護程式執行,VM退出不影響執行,這裡只是為了一個列印效果,去掉註釋一直列印
            worker.setDaemon(true);
            worker.start();
        }
        // 每隔一秒換行
        for (int j = 0; j < 10; j++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println();
        }
    }
}

(3)執行結果

Thread-0 hold lock, Tue Dec 08 16:26:42 CST 2020

Thread-0 release lock, Tue Dec 08 16:26:43 CST 2020
Thread-1 hold lock, Tue Dec 08 16:26:43 CST 2020

Thread-2 hold lock, Tue Dec 08 16:26:44 CST 2020
Thread-1 release lock, Tue Dec 08 16:26:44 CST 2020

Thread-3 hold lock, Tue Dec 08 16:26:45 CST 2020
Thread-2 release lock, Tue Dec 08 16:26:45 CST 2020

Thread-3 release lock, Tue Dec 08 16:26:46 CST 2020
Thread-4 hold lock, Tue Dec 08 16:26:46 CST 2020

Thread-4 release lock, Tue Dec 08 16:26:47 CST 2020
Thread-6 hold lock, Tue Dec 08 16:26:47 CST 2020

Thread-7 hold lock, Tue Dec 08 16:26:48 CST 2020
Thread-6 release lock, Tue Dec 08 16:26:48 CST 2020

Thread-7 release lock, Tue Dec 08 16:26:49 CST 2020
Thread-5 hold lock, Tue Dec 08 16:26:49 CST 2020

Thread-8 hold lock, Tue Dec 08 16:26:50 CST 2020
Thread-5 release lock, Tue Dec 08 16:26:50 CST 2020

Thread-8 release lock, Tue Dec 08 16:26:51 CST 2020
Thread-9 hold lock, Tue Dec 08 16:26:51 CST 2020

(4)結果分析

互斥鎖的核心就是同一個同步狀態只能被一個執行緒持有,其它執行緒等待持有執行緒釋放才能競爭獲取。截圖一開始的執行結果分析:

Thread-0 hold lock, Tue Dec 08 16:26:42 CST 2020

Thread-0 release lock, Tue Dec 08 16:26:43 CST 2020
Thread-1 hold lock, Tue Dec 08 16:26:43 CST 2020

Thread-2 hold lock, Tue Dec 08 16:26:44 CST 2020
Thread-1 release lock, Tue Dec 08 16:26:44 CST 2020

10個執行緒不斷競爭鎖,一開始Thread-0在08 16:26:42獲取到鎖,持有鎖1秒後在釋放16:26:43時釋放,同時Thread-1立馬獲取到鎖,1秒後於16:26:44釋放鎖,同時Thread-2立馬獲取到了鎖......

根據輸出結果來說,完全符合Mutex作為互斥鎖這個功能:同一時刻只有一個執行緒持有鎖(同步狀態),其它執行緒等待釋放後才能獲取

三、指定共享執行緒數目的共享鎖

(1)程式碼實現(核心關鍵實現已經在程式碼中註釋)

public class MyShareLock implements Lock {

    // 可以看到共享等待佇列中的執行緒
    public Collection<Thread> getSharedQueuedThreads(){
        return syn.getSharedQueuedThreads();
    }
    private final Syn syn = new Syn(2);

    private static final class Syn extends AbstractQueuedSynchronizer{
        int newShareCount=0;
        Syn(int shareCount){
            if (shareCount <= 0) {
                throw new IllegalArgumentException("share count must large than zero");
            }
            // 設定初始共享同步狀態
            setState(shareCount);
        }

        /**
         * 共享鎖指定數目
         * @param reduceShareCount
         * @return
         */
        @Override
        protected int tryAcquireShared(int reduceShareCount) {

            for (;;){
                int currentShareCount = getState();
                newShareCount = currentShareCount- reduceShareCount;
                if (newShareCount < 0 ||
                        compareAndSetState(currentShareCount,newShareCount)) {
                    // newShareCount大於等於0才說明獲取鎖成功
                    if (newShareCount >= 0) {
//                        System.out.println(Thread.currentThread().getName()+" hold lock, current share count is "+newShareCount+", "+new Date());
                    }
                    // newShareCount小於0表示獲取失敗所以需要返回
                    // compareAndSetState(currentShareCount,newShareCount)為true自然表示成功需要返回
                    return newShareCount;
                }
            }
        }

        @Override
        protected boolean tryReleaseShared(int returnShareCount) {
            for (;;){
                int currentShareCount = getState();
                newShareCount = currentShareCount + returnShareCount;
                if (compareAndSetState(currentShareCount,newShareCount)) {
//                    System.out.println(Thread.currentThread().getName() +" release lock, current share count is "+newShareCount+", "+new Date());
                    return true;
                }
            }
        }
        protected int getShareCount(){
            return getState();
        }
    }

    /**
     * 呼叫內部同步器Syn的acquireShare方法
     */
    @Override
    public void lock() {
        syn.acquireShared(1);
    }
    /**
     * 呼叫內部同步器Syn的releaseShared方法
     */
    @Override
    public void unlock() {
        syn.releaseShared(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        if (Thread.interrupted()) {
            throw new IllegalStateException();
        }
        syn.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }


    @Override
    public Condition newCondition() {
        return null;
    }
}

(2)測試Demo

public class ShareLockTest {

    @Test
    public void test(){
        final MyShareLock lock = new MyShareLock();
        class Worker extends Thread {
            @Override
            public void run() {
                // 一直不停在獲取鎖
            while (true) {
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() +" hold lock, "+new Date());
//                    System.out.println(lock.getSharedQueuedThreads());
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                    System.out.println(Thread.currentThread().getName() +" release lock, "+new Date());
                }
            }
            }

        }
        for (int i = 0; i < 10; i++) {
            Worker worker = new Worker();
            // 以守護程式執行,VM退出不影響執行,這裡只是為了一個列印效果,去掉註釋一直列印
            worker.setDaemon(true);
            worker.start();
        }
        // 每隔一秒換行
        for (int j = 0; j < 10; j++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println();
        }
    }
}

(3)執行結果(結果可能不同)

Thread-1 hold lock, Tue Dec 08 16:36:05 CST 2020
Thread-0 hold lock, Tue Dec 08 16:36:05 CST 2020

Thread-0 release lock, Tue Dec 08 16:36:06 CST 2020
Thread-4 hold lock, Tue Dec 08 16:36:06 CST 2020
Thread-1 release lock, Tue Dec 08 16:36:06 CST 2020
Thread-2 hold lock, Tue Dec 08 16:36:06 CST 2020

Thread-4 release lock, Tue Dec 08 16:36:07 CST 2020
Thread-2 release lock, Tue Dec 08 16:36:07 CST 2020
Thread-5 hold lock, Tue Dec 08 16:36:07 CST 2020
Thread-8 hold lock, Tue Dec 08 16:36:07 CST 2020

Thread-8 release lock, Tue Dec 08 16:36:08 CST 2020
Thread-3 hold lock, Tue Dec 08 16:36:08 CST 2020
Thread-9 hold lock, Tue Dec 08 16:36:08 CST 2020
Thread-5 release lock, Tue Dec 08 16:36:08 CST 2020

Thread-6 hold lock, Tue Dec 08 16:36:09 CST 2020
Thread-7 hold lock, Tue Dec 08 16:36:09 CST 2020
Thread-3 release lock, Tue Dec 08 16:36:09 CST 2020
Thread-9 release lock, Tue Dec 08 16:36:09 CST 2020

Thread-6 release lock, Tue Dec 08 16:36:10 CST 2020
Thread-1 hold lock, Tue Dec 08 16:36:10 CST 2020
Thread-0 hold lock, Tue Dec 08 16:36:10 CST 2020
Thread-7 release lock, Tue Dec 08 16:36:10 CST 2020

Thread-1 release lock, Tue Dec 08 16:36:11 CST 2020
Thread-2 hold lock, Tue Dec 08 16:36:11 CST 2020
Thread-0 release lock, Tue Dec 08 16:36:11 CST 2020
Thread-4 hold lock, Tue Dec 08 16:36:11 CST 2020

Thread-2 release lock, Tue Dec 08 16:36:12 CST 2020
Thread-8 hold lock, Tue Dec 08 16:36:12 CST 2020
Thread-5 hold lock, Tue Dec 08 16:36:12 CST 2020
Thread-4 release lock, Tue Dec 08 16:36:12 CST 2020

Thread-5 release lock, Tue Dec 08 16:36:13 CST 2020
Thread-9 hold lock, Tue Dec 08 16:36:13 CST 2020
Thread-3 hold lock, Tue Dec 08 16:36:13 CST 2020
Thread-8 release lock, Tue Dec 08 16:36:13 CST 2020

Thread-3 release lock, Tue Dec 08 16:36:14 CST 2020
Thread-7 hold lock, Tue Dec 08 16:36:14 CST 2020
Thread-9 release lock, Tue Dec 08 16:36:14 CST 2020
Thread-6 hold lock, Tue Dec 08 16:36:14 CST 2020

(4)結果分析

該指定共享執行緒數量N的共享鎖的最終目的就是多個執行緒可以持有鎖(同步狀態),達到共享執行緒數量N(程式碼中預設為2)時,其它執行緒將進入Queue等待獲取同步結果,同一時刻只能最多有N個執行緒持有鎖

同樣地,我們分析開頭執行結果:

Thread-1 hold lock, Tue Dec 08 16:36:05 CST 2020
Thread-0 hold lock, Tue Dec 08 16:36:05 CST 2020

Thread-0 release lock, Tue Dec 08 16:36:06 CST 2020
Thread-4 hold lock, Tue Dec 08 16:36:06 CST 2020
Thread-1 release lock, Tue Dec 08 16:36:06 CST 2020
Thread-2 hold lock, Tue Dec 08 16:36:06 CST 2020

10個執行緒不停競爭鎖,一開始Thread-0與Thread-1在16:36:05時刻同時獲取到了鎖,此時已經達到共享數量的最大值,即N,之後持有鎖1秒,Thread-0與Thread-1在16:36:06時刻立馬釋放鎖,同時Thread-4與Thread-2立馬退出等待佇列立馬競爭持有鎖。

從結果來看,完全是符合ShareLock共享鎖功能的:同一時刻最多允許N個執行緒持有鎖,其它執行緒等待持有執行緒釋放鎖

 

相關文章