隊列同步器(AQS)理論與源碼解析

前言

AQS是AbstractQueuedSynchronizer的簡稱,提供了一種實現阻塞鎖和一系列依賴FIFO等待隊列的同步器的框架,如圖所示:在併發包中AQS有着非常重要的地位,本文就來深扒AQS的實現機制。

正文

同步器使用

在這裏以ReentrantLock爲例,同步器的主要使用方式是繼承並實現抽象方法管理同步狀態,可以看下源碼:

同步器提供了三個用來管理同步狀態的方法,分別是
 

protected final int getState() {
        return state;
    }

   
    protected final void setState(int newState) {
        state = newState;
    }

 
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

AQS的子類通常定義爲同步組件的靜態內部類,如同ReentrantLock中所示,因爲同步器本身沒有實現任何接口,僅僅定義了同步狀態的操作方法供組件使用。此外,同步器支持獨佔氏和共享氏兩種獲取資源的狀態,基於此就可以實現自定義的組件(例如ReentrantLock、ReentrantReadWriteLock、CountDownLatch),這裏給大家解釋一下獨佔氏與共享氏,獨佔氏指的是某個資源特定時間只能有一個線程獲取鎖,其他獲取這個鎖的線程只能處理同步隊列中等待,只有當前線程釋放了鎖,後續線程採用獲取鎖。共享氏:在同一個時刻可以有多個線程獲取同步狀態,比如讀寫鎖

同步器可重寫方法

同步器的設計是基於模版方法模式,使用者需要重寫指定的方法,然後將同步器組合在自己的組件中使用,同步器提供了以下可重寫的方法,在調用的過程中會使用重寫的方法

  • boolean tryAcquire(int arg): 獨佔式的獲取同步狀態,實現該方法需要先獲取並判斷同步狀態是否符合預期,再通過CAS設置同步狀態。(即使用compareAndSetState(expect, update)方法)。可用於實現Lock接口中的tryLock()方法。=
  • boolean tryRelease(int arg): 獨佔式的釋放同步狀態,等待獲取同步狀態的線程將有機會獲取同步狀態,實現Lock的unlock()。
  • int tryAcquireShared(int arg): 共享式的獲取同步狀態,返回大於等於0的值表示獲取成功,否則獲取失敗。
  • boolean tryReleaseShared(int arg): 共享式的釋放同步狀態
  • boolean isHeldExclusively(): 用於獲取同步器是否在獨佔模式下被線程佔用,一般用來表示是否被當前線程所獨佔。

同步器的模版方法

實現自定義的同步組件,需要調用同步器中模板方法,而模板方法內部則調用的是同步器的可重寫方法

  • void acquire(int arg): 獨佔式的獲取同步狀態,會調用tryAcquire()重寫方法。如果當前線程獲取同步狀態成功,則直接返回;否則,進入同步隊列等待。
  • void acquireInterruptibly(int arg): 與acquire()相同,但可以響應中斷。如果當前線程獲取同步狀態失敗,進入同步隊列,直到獲取成功或響應中斷。如果響應中斷,會拋出中斷異常(InterruptException)。
  • boolean tryAcquireNanos(int arg, long nanosTimeout): 在acquireInterruptibly()的基礎上增加了等待時間,如果等待超時,則返回false;如果在等待的時間內,成功獲取則返回true。
  • boolean release(int arg): 獨佔式的釋放同步狀態,並在釋放同步狀態之後,喚醒同步隊列中第一個節點包含的線程。
  • void acquireShared(int arg): 共享式的獲取同步鎖,與獨佔式獲取的主要區別:同一時刻可以有多個線程獲取到同步狀態。
  • void acquireSharedInterruptibly(int arg): 與acquireShared()相同,但可以響應中斷。
  • boolean tryAcquireSharedNanos(int arg, long nanosTimeout): 在acquireSharedInterruptibly()的基礎上增加了等待時間。
  • boolean releaseShared(int arg): 共享式的釋放同步狀態
  • Collection getQueuedThreads(): 獲取在同步隊列中等待的線程集合。

自定義組件的例子

這個例子是實現的一個獨佔氏的鎖,對於重寫方法、模版方法、同步器的組合都非常明白的定義了出來

public class Mutex implements Lock {

    /**
     * 隊列同步器的extend
     */
    private static class Sync extends AbstractQueuedSynchronizer{

        /**
         * 是否處於佔用狀態
         * @return
         */
        @Override
        protected boolean isHeldExclusively(){
            return getState()==1;
    }

        /**
         * 狀態爲0時獲取鎖
         * @param acquires
         * @return
         */
        @Override
        public boolean tryAcquire(int acquires){
            if (compareAndSetState(0,1)){
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        @Override
        protected boolean tryRelease(int release){
            if (getState()==0){
                throw new IllegalMonitorStateException();
            }

            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        Condition newCondition(){
            return new ConditionObject();
        }
    }

    private final Sync sync = new Sync();

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

    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireSharedNanos(1,unit.toNanos(time));
    }

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

    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }
}

隊列同步器的實現

前面在介紹資源的獲取方式的時候提到過同步隊列,沒有獲取到鎖的線程將會同步隊列中等待。那麼這個同步隊列是如何實現的呢?

在同步器的內部維護了一個FIFO的同步隊列,同步器內部定義了Node節點,節點包含了線程需要獲取資源的各種信息,同步隊列就是由這些節點鏈接而成的,並且指明瞭頭/尾節點。

同步隊列如圖所示(來自網絡):千萬別說這是個鏈表(因爲我想過,哈哈)這可是滿足FIFO原則的

最後我們看一下線程獲取獨佔氏獲取鎖的流程

 節點加入隊列尾部的源碼如下:

嘗試循環添加節點

最後節點添加成功後,就進入自旋狀態,不斷的判斷條件,滿足條件就獲取同步狀態


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章