聊聊JUC包下的底層支撐類-AbstractQueuedSynchronizer(AQS)

木木他爹發表於2023-02-07

聊聊JUC包下的底層支撐類-AbstractQueuedSynchronizer(AQS)

juc包下的一堆併發工具類是我們日常開發特別是面試中常被拿來問的八股文之一,為了工作也好,為了面試也罷,今天開始想嘗試著把這些給大夥描述明白,所以開始寫下這篇博文,如果後續要涉及每個常用類的原始碼的話可能會是一個系列,計劃從比較底層的AQS聊起,然後結合ReentrantLock的原始碼來聊AQS獨佔鎖的具體實現,以及加鎖和釋放鎖的過程;然後再聊聊JUC包下其他類如CountDownLatch、CyclicBarrier、Phaser、ReadWriteLock、Semaphore、Exchanger以及LockSupport的使用和原理,有時間的話再結合CountDownLatch的原始碼來聊AQS共享鎖的具體實現,接下來話不多說開始踏上揭秘AQS之旅

一、AQS是什麼

  • AQS全稱AbstractQueuedSynchronizer即抽象佇列同步器,可以理解成是一個可以實現鎖的框架(基類),它可以實現共享和獨佔兩種模式的鎖,事實上juc包下很多關於鎖的工具類也是基於AQS的;它提供了一些模板方法供子類實現擴充,並且本身結合底層的Unsafe類實現了基於cpu原語層的安全操作,從而實現在併發環境下的執行緒安全

二、AQS的實現原理

  • AQS能作為基類來實現鎖的功能主要原因來自於它維護的一個int型別的state變數和一個FIFO的雙向佇列;實現類可以根據自身的需求,透過控制state的值來決定執行緒是否需要阻塞,而雙向佇列用來存放沒有爭搶到鎖資源的執行緒;並且AQS透過結合Unsafe類的能力封裝了可以執行緒安全的操作state值的方法(一堆CAS的操作方法),這樣程式設計師就可以只關注鎖的使用而不必關注底層實現的細節了;
  • AQS支援兩種模式鎖的實現,分別是獨佔鎖和共享鎖,獨佔鎖的具體實現以ReentrantLock為代表,共享鎖的實現諸如CountDownLatch、CyclicBarrier等

注:由於後續會介紹AQS的原始碼以及子類實現,所以這裡只是大白話般的描述了一下AQS的原理,即兩個關鍵:一個state一個雙向佇列,其實要展開還有許多細節要聊,考慮到這些細節後續原始碼裡會有體現這裡就不再表述了

三、AQS的原始碼簡析

1、state變數

private volatile int state;

1、state是AQS提供的供子類擴充的一個同步狀態,子類可以維護state的不同狀態來實現不同效果的鎖實現,如ReentrantLock就是透過維護state是否為0或1來表示鎖的加解操作;

2、用volatile修飾主要是為了在併發環境下執行緒可見

2、Node內部類

  • Node類是雙向CLH佇列的構成元素,其維護的thread變數就是沒有爭搶到鎖的執行緒,然後還維護了CLH佇列的其他幾個關鍵資訊,如當前Node的前置節點(prev)、後續節點(next)等,下面貼上Node的原始碼
static final class Node {
    /** 表示當前節點正處於共享模式 */
    static final Node SHARED = new Node();
    /** 表示當前節點正處於獨佔模式 */
    static final Node EXCLUSIVE = null;

    /** waitStatus對應的值,表示執行緒已取消 */
    static final int CANCELLED =  1;
    /** waitStatus對應的值,指示後續執行緒需要取消標記*/
    static final int SIGNAL    = -1;
    /** waitStatus對應的值,指示執行緒正在等待condition喚醒*/
    static final int CONDITION = -2;
    /**
     * waitStatus value to indicate the next acquireShared should
     * unconditionally propagate
     */
    static final int PROPAGATE = -3;

    /**
     * 等待狀態,列舉值有1,0,-1,-2,-3分別對應上面的幾個變數值,0表示以上狀態都不是
     */
    volatile int waitStatus;

    /**
     * 當前Node的前置節點
     */
    volatile Node prev;

    /**
     * 當前Node的後繼節點
     */
    volatile Node next;

    /**
     * 與當前Node繫結的被阻塞的執行緒
     */
    volatile Thread thread;

    /**
     * 下一個等待節點,Condition狀態下要用到
     */
    Node nextWaiter;

    /**
     * 如果節點在共享模式下等待,則返回true。
     */
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    /**
     * 獲取當前佇列的前置節點
     *
     * @return the predecessor of this node
     */
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    Node() {    // Used to establish initial head or SHARED marker
    }

    Node(Thread thread, Node mode) {     // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }

    Node(Thread thread, int waitStatus) { // Used by Condition
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

3、AQS供子類擴充的模板方法

獨佔模式供子類實現的方法

  • tryAcquire(int) 嘗試獲取鎖,獲取成功返回true,失敗返回false

  • tryRelease(int) 嘗試釋放鎖,釋放成功返回true,失敗返回false

共享模式供子類實現的方法

  • tryAcquireshared(int)

    嘗試獲取鎖,負數表示失敗; 0表示功,但沒有剩餘可用資源:正數表示成功,且有剩餘資源。

  • tryReleaseshared(int)

    嘗試釋放鎖,成功返回true,失敗返回false

上面簡單介紹了一下AQS的原理以及原始碼的部分註釋,接下來我會寫一篇ReentrantLock原始碼解讀的相關文章,來體驗下AQS的實際用處

相關文章