一文秒懂Java併發中最重要的類AbstractQueuedSynchronizer(AQS)




在分析 Java 併發包 java.util.concurrent 源碼的時候,少不了需要了解 AbstractQueuedSynchronizer(以下簡寫AQS)這個抽象類,因為它是 Java 併發包的基礎工具類,是實現 ReentrantLock、CountDownLatch、Semaphore、FutureTask 等類的基礎。


Google 一下 AbstractQueuedSynchronizer,我們可以找到很多關於 AQS 的介紹,但是很多都沒有介紹清楚,因為大部分文章沒有把其中的一些關鍵的細節說清楚。

本文將從 ReentrantLock 的公平鎖源碼出發,分析下 AbstractQueuedSynchronizer 這個類是怎麼工作的,希望能給大家提供一些簡單的幫助。

申明以下幾點:

  1. 本文有點長,但還是挺簡單,主要面向讀者對象為併發編程的初學者,或者想要閱讀 Java 併發包源碼的開發者。對於新手來說,可能需要花好幾個小時才能完全看懂,但是這時間肯定是值得的。
  2. 源碼環境 JDK1.7(1.8沒啥變化),看到不懂或有疑惑的部分,最好能自己打開源碼看看。Doug Lea 大神的代碼寫得真心不錯。
  3. 本文不分析共享模式。
  4. 本文大量使用我們平時用得最多的 ReentrantLock 的概念,本質上來說是不正確的,讀者應該清楚,AQS 不僅僅用來實現可重入鎖,只是希望讀者可以用鎖來聯想 AQS 的使用場景,降低閱讀壓力。
  5. ReentrantLock 的公平鎖和非公平鎖只有一點點區別
  6. 評論區有讀者反饋本文直接用代碼說不友好,應該多配點流程圖,這篇文章確實有這個問題。但是作為過來人,我想告訴大家,對於 AQS 來說,形式真的不重要,重要的是把細節說清楚。

AQS 結構

先來看看 AQS 有哪些屬性,搞清楚這些基本就知道 AQS 是什麼套路了,畢竟可以猜嘛!

<code>// 頭結點,你直接把它當做 當前持有鎖的線程 可能是最好理解的private transient volatile Node head;// 阻塞的尾節點,每個新的節點進來,都插入到最後,也就形成了一個鏈表private transient volatile Node tail;// 這個是最重要的,代表當前鎖的狀態,0代表沒有被佔用,大於 0 代表有線程持有當前鎖// 這個值可以大於 1,是因為鎖可以重入,每次重入都加上 1private volatile int state;// 代表當前持有獨佔鎖的線程,舉個最重要的使用例子,因為鎖可以重入// reentrantLock.lock()可以嵌套調用多次,所以每次用這個來判斷當前線程是否已經擁有了鎖// if (currentThread == getExclusiveOwnerThread()) {state++}private transient Thread exclusiveOwnerThread; //繼承自AbstractOwnableSynchronizer/<code> 

怎麼樣,看樣子應該是很簡單的吧,畢竟也就四個屬性啊。

AbstractQueuedSynchronizer 的等待隊列示意如下所示,注意了,之後分析過程中所說的 queue,也就是阻塞隊列不包含 head,不包含 head,不包含 head


一文秒懂Java併發中最重要的類AbstractQueuedSynchronizer(AQS)


等待隊列中每個線程被包裝成一個 Node 實例,數據結構是鏈表,一起看看源碼吧:

<code>static final class Node {    // 標識節點當前在共享模式下    static final Node SHARED = new Node();    // 標識節點當前在獨佔模式下    static final Node EXCLUSIVE = null;      // ======== 下面的幾個int常量是給waitStatus用的 ===========    /** waitStatus value to indicate thread has cancelled */    // 代碼此線程取消了爭搶這個鎖    static final int CANCELLED =  1;    /** waitStatus value to indicate successor's thread needs unparking */    // 官方的描述是,其表示當前node的後繼節點對應的線程需要被喚醒    static final int SIGNAL    = -1;    /** waitStatus value to indicate thread is waiting on condition */    // 本文不分析condition,所以略過吧,下一篇文章會介紹這個    static final int CONDITION = -2;    /**     * waitStatus value to indicate the next acquireShared should     * unconditionally propagate     */    // 同樣的不分析,略過吧    static final int PROPAGATE = -3;    // =====================================================        // 取值為上面的1、-1、-2、-3,或者0(以後會講到)    // 這麼理解,暫時只需要知道如果這個值 大於0 代表此線程取消了等待,    //    ps: 半天搶不到鎖,不搶了,ReentrantLock是可以指定timeouot的。。。    volatile int waitStatus;    // 前驅節點的引用    volatile Node prev;    // 後繼節點的引用    volatile Node next;    // 這個就是線程本尊    volatile Thread thread;}/<code>

Node 的數據結構其實也挺簡單的,就是 thread + waitStatus + pre + next 四個屬性而已,大家先要有這個概念在心裡。

上面的是基礎知識,後面會多次用到,心裡要時刻記著它們,心裡想著這個結構圖就可以了。下面,我們開始說 ReentrantLock 的公平鎖。再次強調,我說的阻塞隊列不包含 head 節點。


一文秒懂Java併發中最重要的類AbstractQueuedSynchronizer(AQS)


首先,我們先看下 ReentrantLock 的使用方式。

<code>// 我用個web開發中的service概念吧public class OrderService {    // 使用static,這樣每個線程拿到的是同一把鎖,當然,spring mvc中service默認就是單例,別糾結這個    private static ReentrantLock reentrantLock = new ReentrantLock(true);        public void createOrder() {        // 比如我們同一時間,只允許一個線程創建訂單        reentrantLock.lock();        // 通常,lock 之後緊跟著 try 語句        try {            // 這塊代碼同一時間只能有一個線程進來(獲取到鎖的線程),            // 其他的線程在lock()方法上阻塞,等待獲取到鎖,再進來            // 執行代碼...            // 執行代碼...            // 執行代碼...        } finally {            // 釋放鎖            reentrantLock.unlock();        }    }}/<code>

ReentrantLock 在內部用了內部類 Sync 來管理鎖,所以真正的獲取鎖和釋放鎖是由 Sync 的實現類來控制的。

<code>abstract static class Sync extends AbstractQueuedSynchronizer {}/<code>

Sync 有兩個實現,分別為 NonfairSync(非公平鎖)和 FairSync(公平鎖),我們看 FairSync 部分。

<code>public ReentrantLock(boolean fair) {    sync = fair ? new FairSync() : new NonfairSync();}/<code>

線程搶鎖

很多人肯定開始嫌棄上面廢話太多了,下面跟著代碼走,我就不廢話了。

<code>static final class FairSync extends Sync {    private static final long serialVersionUID = -3000897897090466540L;  // 爭鎖    final void lock() {        acquire(1);    }  // 來自父類AQS,我直接貼過來這邊,下面分析的時候同樣會這樣做,不會給讀者帶來閱讀壓力    // 我們看到,這個方法,如果tryAcquire(arg) 返回true, 也就結束了。    // 否則,acquireQueued方法會將線程壓到隊列中    public final void acquire(int arg) { // 此時 arg == 1        // 首先調用tryAcquire(1)一下,名字上就知道,這個只是試一試        // 因為有可能直接就成功了呢,也就不需要進隊列排隊了,        // 對於公平鎖的語義就是:本來就沒人持有鎖,根本沒必要進隊列等待(又是掛起,又是等待被喚醒的)        if (!tryAcquire(arg) &&            // tryAcquire(arg)沒有成功,這個時候需要把當前線程掛起,放到阻塞隊列中。            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {              selfInterrupt();        }    }    /**     * Fair version of tryAcquire.  Don't grant access unless     * recursive call or no waiters or is first.     */    // 嘗試直接獲取鎖,返回值是boolean,代表是否獲取到鎖    // 返回true:1.沒有線程在等待鎖;2.重入鎖,線程本來就持有鎖,也就可以理所當然可以直接獲取    protected final boolean tryAcquire(int acquires) {        final Thread current = Thread.currentThread();        int c = getState();        // state == 0 此時此刻沒有線程持有鎖        if (c == 0) {            // 雖然此時此刻鎖是可以用的,但是這是公平鎖,既然是公平,就得講究先來後到,            // 看看有沒有別人在隊列中等了半天了            if (!hasQueuedPredecessors() &&                // 如果沒有線程在等待,那就用CAS嘗試一下,成功了就獲取到鎖了,                // 不成功的話,只能說明一個問題,就在剛剛幾乎同一時刻有個線程搶先了 =_=                // 因為剛剛還沒人的,我判斷過了                compareAndSetState(0, acquires)) {                              // 到這裡就是獲取到鎖了,標記一下,告訴大家,現在是我佔用了鎖                setExclusiveOwnerThread(current);                return true;            }        }      // 會進入這個else if分支,說明是重入了,需要操作:state=state+1        // 這裡不存在併發問題        else if (current == getExclusiveOwnerThread()) {            int nextc = c + acquires;            if (nextc < 0)                throw new Error("Maximum lock count exceeded");            setState(nextc);            return true;        }        // 如果到這裡,說明前面的if和else if都沒有返回true,說明沒有獲取到鎖        // 回到上面一個外層調用方法繼續看:        // if (!tryAcquire(arg)         //        && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))         //     selfInterrupt();        return false;    }      // 假設tryAcquire(arg) 返回false,那麼代碼將執行:  //acquireQueued(addWaiter(Node.EXCLUSIVE), arg),    // 這個方法,首先需要執行:addWaiter(Node.EXCLUSIVE)      /**     * Creates and enqueues node for current thread and given mode.     *     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared     * @return the new node     */    // 此方法的作用是把線程包裝成node,同時進入到隊列中    // 參數mode此時是Node.EXCLUSIVE,代表獨佔模式    private Node addWaiter(Node mode) {        Node node = new Node(Thread.currentThread(), mode);        // Try the fast path of enq; backup to full enq on failure        // 以下幾行代碼想把當前node加到鏈表的最後面去,也就是進到阻塞隊列的最後        Node pred = tail;              // tail!=null => 隊列不為空(tail==head的時候,其實隊列是空的,不過不管這個吧)        if (pred != null) {             // 將當前的隊尾節點,設置為自己的前驅             node.prev = pred;             // 用CAS把自己設置為隊尾, 如果成功後,tail == node 了,這個節點成為阻塞隊列新的尾巴            if (compareAndSetTail(pred, node)) {                 // 進到這裡說明設置成功,當前node==tail, 將自己與之前的隊尾相連,                // 上面已經有 node.prev = pred,加上下面這句,也就實現了和之前的尾節點雙向連接了                pred.next = node;                // 線程入隊了,可以返回了                return node;            }        }        // 仔細看看上面的代碼,如果會到這裡,        // 說明 pred==null(隊列是空的) 或者 CAS失敗(有線程在競爭入隊)        // 讀者一定要跟上思路,如果沒有跟上,建議先不要往下讀了,往回仔細看,否則會浪費時間的        enq(node);        return node;    }      /**     * Inserts node into queue, initializing if necessary. See picture above.     * @param node the node to insert     * @return node's predecessor     */    // 採用自旋的方式入隊    // 之前說過,到這個方法只有兩種可能:等待隊列為空,或者有線程競爭入隊,    // 自旋在這邊的語義是:CAS設置tail過程中,競爭一次競爭不到,我就多次競爭,總會排到的    private Node enq(final Node node) {        for (;;) {            Node t = tail;            // 之前說過,隊列為空也會進來這裡            if (t == null) { // Must initialize                // 初始化head節點                // 細心的讀者會知道原來 head 和 tail 初始化的時候都是 null 的                // 還是一步CAS,你懂的,現在可能是很多線程同時進來呢                if (compareAndSetHead(new Node()))                    // 給後面用:這個時候head節點的waitStatus==0, 看new Node()構造方法就知道了                                      // 這個時候有了head,但是tail還是null,設置一下,                    // 把tail指向head,放心,馬上就有線程要來了,到時候tail就要被搶了                    // 注意:這裡只是設置了tail=head,這裡可沒return哦,沒有return,沒有return                    // 所以,設置完了以後,繼續for循環,下次就到下面的else分支了                    tail = head;            } else {                // 下面幾行,和上一個方法 addWaiter 是一樣的,                // 只是這個套在無限循環裡,反正就是將當前線程排到隊尾,有線程競爭的話排不上重複排                node.prev = t;                if (compareAndSetTail(t, node)) {                    t.next = node;                    return t;                }            }        }    }          // 現在,又回到這段代碼了    // if (!tryAcquire(arg)     //        && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))     //     selfInterrupt();        // 下面這個方法,參數node,經過addWaiter(Node.EXCLUSIVE),此時已經進入阻塞隊列    // 注意一下:如果acquireQueued(addWaiter(Node.EXCLUSIVE), arg))返回true的話,    // 意味著上面這段代碼將進入selfInterrupt(),所以正常情況下,下面應該返回false    // 這個方法非常重要,應該說真正的線程掛起,然後被喚醒後去獲取鎖,都在這個方法裡了    final boolean acquireQueued(final Node node, int arg) {        boolean failed = true;        try {            boolean interrupted = false;            for (;;) {                final Node p = node.predecessor();                // p == head 說明當前節點雖然進到了阻塞隊列,但是是阻塞隊列的第一個,因為它的前驅是head                // 注意,阻塞隊列不包含head節點,head一般指的是佔有鎖的線程,head後面的才稱為阻塞隊列                // 所以當前節點可以去試搶一下鎖                // 這裡我們說一下,為什麼可以去試試:                // 首先,它是隊頭,這個是第一個條件,其次,當前的head有可能是剛剛初始化的node,                // enq(node) 方法裡面有提到,head是延時初始化的,而且new Node()的時候沒有設置任何線程                // 也就是說,當前的head不屬於任何一個線程,所以作為隊頭,可以去試一試,                // tryAcquire已經分析過了, 忘記了請往前看一下,就是簡單用CAS試操作一下state                if (p == head && tryAcquire(arg)) {                    setHead(node);                    p.next = null; // help GC                    failed = false;                    return interrupted;                }                // 到這裡,說明上面的if分支沒有成功,要麼當前node本來就不是隊頭,                // 要麼就是tryAcquire(arg)沒有搶贏別人,繼續往下看                if (shouldParkAfterFailedAcquire(p, node) &&                    parkAndCheckInterrupt())                    interrupted = true;            }        } finally {            // 什麼時候 failed 會為 true???            // tryAcquire() 方法拋異常的情況            if (failed)                cancelAcquire(node);        }    }      /**     * Checks and updates status for a node that failed to acquire.     * Returns true if thread should block. This is the main signal     * control in all acquire loops.  Requires that pred == node.prev     *     * @param pred node's predecessor holding status     * @param node the node     * @return {@code true} if thread should block     */    // 剛剛說過,會到這裡就是沒有搶到鎖唄,這個方法說的是:"當前線程沒有搶到鎖,是否需要掛起當前線程?"    // 第一個參數是前驅節點,第二個參數才是代表當前線程的節點    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {        int ws = pred.waitStatus;        // 前驅節點的 waitStatus == -1 ,說明前驅節點狀態正常,當前線程需要掛起,直接可以返回true        if (ws == Node.SIGNAL)            /*             * This node has already set status asking a release             * to signal it, so it can safely park.             */            return true;                // 前驅節點 waitStatus大於0 ,之前說過,大於0 說明前驅節點取消了排隊。        // 這裡需要知道這點:進入阻塞隊列排隊的線程會被掛起,而喚醒的操作是由前驅節點完成的。        // 所以下面這塊代碼說的是將當前節點的prev指向waitStatus<=0的節點,        // 簡單說,就是為了找個好爹,因為你還得依賴它來喚醒呢,如果前驅節點取消了排隊,        // 找前驅節點的前驅節點做爹,往前遍歷總能找到一個好爹的        if (ws > 0) {            /*             * Predecessor was cancelled. Skip over predecessors and             * indicate retry.             */            do {                node.prev = pred = pred.prev;            } while (pred.waitStatus > 0);            pred.next = node;        } else {            /*             * waitStatus must be 0 or PROPAGATE.  Indicate that we             * need a signal, but don't park yet.  Caller will need to             * retry to make sure it cannot acquire before parking.             */            // 仔細想想,如果進入到這個分支意味著什麼            // 前驅節點的waitStatus不等於-1和1,那也就是隻可能是0,-2,-3            // 在我們前面的源碼中,都沒有看到有設置waitStatus的,所以每個新的node入隊時,waitStatu都是0            // 正常情況下,前驅節點是之前的 tail,那麼它的 waitStatus 應該是 0            // 用CAS將前驅節點的waitStatus設置為Node.SIGNAL(也就是-1)            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);        }        // 這個方法返回 false,那麼會再走一次 for 循序,        //     然後再次進來此方法,此時會從第一個分支返回 true        return false;    }      // private static boolean shouldParkAfterFailedAcquire(Node pred, Node node)    // 這個方法結束根據返回值我們簡單分析下:    // 如果返回true, 說明前驅節點的waitStatus==-1,是正常情況,那麼當前線程需要被掛起,等待以後被喚醒    //我們也說過,以後是被前驅節點喚醒,就等著前驅節點拿到鎖,然後釋放鎖的時候叫你好了    // 如果返回false, 說明當前不需要被掛起,為什麼呢?往後看      // 跳回到前面是這個方法    // if (shouldParkAfterFailedAcquire(p, node) &&    //                parkAndCheckInterrupt())    //                interrupted = true;        // 1. 如果shouldParkAfterFailedAcquire(p, node)返回true,    // 那麼需要執行parkAndCheckInterrupt():      // 這個方法很簡單,因為前面返回true,所以需要掛起線程,這個方法就是負責掛起線程的    // 這裡用了LockSupport.park(this)來掛起線程,然後就停在這裡了,等待被喚醒=======    private final boolean parkAndCheckInterrupt() {        LockSupport.park(this);        return Thread.interrupted();    }      // 2. 接下來說說如果shouldParkAfterFailedAcquire(p, node)返回false的情況     // 仔細看shouldParkAfterFailedAcquire(p, node),我們可以發現,其實第一次進來的時候,一般都不會返回true的,原因很簡單,前驅節點的waitStatus=-1是依賴於後繼節點設置的。也就是說,我都還沒給前驅設置-1呢,怎麼可能是true呢,但是要看到,這個方法是套在循環裡的,所以第二次進來的時候狀態就是-1了。      // 解釋下為什麼shouldParkAfterFailedAcquire(p, node)返回false的時候不直接掛起線程:    // => 是為了應對在經過這個方法後,node已經是head的直接後繼節點了。剩下的讀者自己想想吧。}/<code> 

說到這裡,也就明白了,多看幾遍 final boolean acquireQueued(final Node node, int arg) 這個方法吧。自己推演下各個分支怎麼走,哪種情況下會發生什麼,走到哪裡。

解鎖操作

最後,就是還需要介紹下喚醒的動作了。我們知道,正常情況下,如果線程沒獲取到鎖,線程會被 LockSupport.park(this); 掛起停止,等待被喚醒。

<code>// 喚醒的代碼還是比較簡單的,你如果上面加鎖的都看懂了,下面都不需要看就知道怎麼回事了public void unlock() {    sync.release(1);}public final boolean release(int arg) {    // 往後看吧    if (tryRelease(arg)) {        Node h = head;        if (h != null && h.waitStatus != 0)            unparkSuccessor(h);        return true;    }    return false;}// 回到ReentrantLock看tryRelease方法protected final boolean tryRelease(int releases) {    int c = getState() - releases;    if (Thread.currentThread() != getExclusiveOwnerThread())        throw new IllegalMonitorStateException();    // 是否完全釋放鎖    boolean free = false;    // 其實就是重入的問題,如果c==0,也就是說沒有嵌套鎖了,可以釋放了,否則還不能釋放掉    if (c == 0) {        free = true;        setExclusiveOwnerThread(null);    }    setState(c);    return free;}/** * Wakes up node's successor, if one exists. * * @param node the node */// 喚醒後繼節點// 從上面調用處知道,參數node是head頭結點private void unparkSuccessor(Node node) {    /*     * If status is negative (i.e., possibly needing signal) try     * to clear in anticipation of signalling.  It is OK if this     * fails or if status is changed by waiting thread.     */    int ws = node.waitStatus;    // 如果head節點當前waitStatus<0, 將其修改為0    if (ws < 0)        compareAndSetWaitStatus(node, ws, 0);    /*     * Thread to unpark is held in successor, which is normally     * just the next node.  But if cancelled or apparently null,     * traverse backwards from tail to find the actual     * non-cancelled successor.     */    // 下面的代碼就是喚醒後繼節點,但是有可能後繼節點取消了等待(waitStatus==1)    // 從隊尾往前找,找到waitStatus<=0的所有節點中排在最前面的    Node s = node.next;    if (s == null || s.waitStatus > 0) {        s = null;        // 從後往前找,仔細看代碼,不必擔心中間有節點取消(waitStatus==1)的情況        for (Node t = tail; t != null && t != node; t = t.prev)            if (t.waitStatus <= 0)                s = t;    }    if (s != null)        // 喚醒線程        LockSupport.unpark(s.thread);}/<code>

喚醒線程以後,被喚醒的線程將從以下代碼中繼續往前走:

<code>private final boolean parkAndCheckInterrupt() {    LockSupport.park(this); // 剛剛線程被掛起在這裡了    return Thread.interrupted();}// 又回到這個方法了:acquireQueued(final Node node, int arg),這個時候,node的前驅是head了/<code>

好了,後面就不分析源碼了,剩下的還有問題自己去仔細看看代碼吧。

總結

總結一下吧。

在併發環境下,加鎖和解鎖需要以下三個部件的協調:

  1. 鎖狀態。我們要知道鎖是不是被別的線程佔有了,這個就是 state 的作用,它為 0 的時候代表沒有線程佔有鎖,可以去爭搶這個鎖,用 CAS 將 state 設為 1,如果 CAS 成功,說明搶到了鎖,這樣其他線程就搶不到了,如果鎖重入的話,state進行 +1 就可以,解鎖就是減 1,直到 state 又變為 0,代表釋放鎖,所以 lock() 和 unlock() 必須要配對啊。然後喚醒等待隊列中的第一個線程,讓其來佔有鎖。
  2. 線程的阻塞和解除阻塞。AQS 中採用了 LockSupport.park(thread) 來掛起線程,用 unpark 來喚醒線程。
  3. 阻塞隊列。因為爭搶鎖的線程可能很多,但是隻能有一個線程拿到鎖,其他的線程都必須等待,這個時候就需要一個 queue 來管理這些線程,AQS 用的是一個 FIFO 的隊列,就是一個鏈表,每個 node 都持有後繼節點的引用。AQS 採用了 CLH 鎖的變體來實現,感興趣的讀者可以參考這篇文章關於CLH的介紹,寫得簡單明瞭。

示例圖解析

下面屬於回顧環節,用簡單的示例來說一遍,如果上面的有些東西沒看懂,這裡還有一次幫助你理解的機會。

首先,第一個線程調用 reentrantLock.lock(),翻到最前面可以發現,tryAcquire(1) 直接就返回 true 了,結束。只是設置了 state=1,連 head 都沒有初始化,更談不上什麼阻塞隊列了。要是線程 1 調用 unlock() 了,才有線程 2 來,那世界就太太太平了,完全沒有交集嘛,那我還要 AQS 幹嘛。

如果線程 1 沒有調用 unlock() 之前,線程 2 調用了 lock(), 想想會發生什麼?

線程 2 會初始化 head【new Node()】,同時線程 2 也會插入到阻塞隊列並掛起 (注意看這裡是一個 for 循環,而且設置 head 和 tail 的部分是不 return 的,只有入隊成功才會跳出循環)

<code>private Node enq(final Node node) {    for (;;) {        Node t = tail;        if (t == null) { // Must initialize            if (compareAndSetHead(new Node()))                tail = head;        } else {            node.prev = t;            if (compareAndSetTail(t, node)) {                t.next = node;                return t;            }        }    }}/<code>

首先,是線程 2 初始化 head 節點,此時 head==tail, waitStatus==0


一文秒懂Java併發中最重要的類AbstractQueuedSynchronizer(AQS)


然後線程 2 入隊:


一文秒懂Java併發中最重要的類AbstractQueuedSynchronizer(AQS)


同時我們也要看此時節點的 waitStatus,我們知道 head 節點是線程 2 初始化的,此時的 waitStatus 沒有設置, java 默認會設置為 0,但是到 shouldParkAfterFailedAcquire 這個方法的時候,線程 2 會把前驅節點,也就是 head 的waitStatus設置為 -1。

那線程 2 節點此時的 waitStatus 是多少呢,由於沒有設置,所以是 0;

如果線程 3 此時再進來,直接插到線程 2 的後面就可以了,此時線程 3 的 waitStatus 是 0,到 shouldParkAfterFailedAcquire 方法的時候把前驅節點線程 2 的 waitStatus 設置為 -1。


一文秒懂Java併發中最重要的類AbstractQueuedSynchronizer(AQS)


這裡可以簡單說下 waitStatus 中 SIGNAL(-1) 狀態的意思,Doug Lea 註釋的是:代表後繼節點需要被喚醒。也就是說這個 waitStatus 其實代表的不是自己的狀態,而是後繼節點的狀態,我們知道,每個 node 在入隊的時候,都會把前驅節點的狀態改為 SIGNAL,然後阻塞,等待被前驅喚醒。這裡涉及的是兩個問題:有線程取消了排隊、喚醒操作。其實本質是一樣的,讀者也可以順著 “waitStatus代表後繼節點的狀態” 這種思路去看一遍源碼。


作者:千珏
鏈接:https://juejin.im/post/5e11e4a35188253a782d8456


分享到:


相關文章: