1. 程式人生 > >深入理解Java中的AQS

深入理解Java中的AQS

AQS概述

​ AbstractQueuedSynchronizer抽象佇列同步器簡稱AQS,它是實現同步器的基礎元件,juc下面Lock的實現以及一些併發工具類就是通過AQS來實現的,這裡我們通過AQS的類圖先看一下大概,下面我們總結一下AQS的實現原理。先看看AQS的類圖。

​ (1)AQS是一個通過內建的FIFO雙向佇列來完成執行緒的排隊工作(內部通過結點head和tail記錄隊首和隊尾元素,元素的結點型別為Node型別,後面我們會看到Node的具體構造)。

/*等待佇列的隊首結點(懶載入,這裡體現為競爭失敗的情況下,加入同步佇列的執行緒執行到enq方法的時候會創
建一個Head結點)。該結點只能被setHead方法修改。並且結點的waitStatus不能為CANCELLED*/
private transient volatile Node head;
/**等待佇列的尾節點,也是懶載入的。(enq方法)。只在加入新的阻塞結點的情況下修改*/
private transient volatile Node tail;

​ (2)其中Node中的thread用來存放進入AQS佇列中的執行緒引用,Node結點內部的SHARED表示標記執行緒是因為獲取共享資源失敗被阻塞新增到佇列中的;Node中的EXCLUSIVE表示執行緒因為獲取獨佔資源失敗被阻塞新增到佇列中的。waitStatus表示當前執行緒的等待狀態:

​ ①CANCELLED=1:表示執行緒因為中斷或者等待超時,需要從等待佇列中取消等待;

​ ②SIGNAL=-1:當前執行緒thread1佔有鎖,佇列中的head(僅僅代表頭結點,裡面沒有存放執行緒引用)的後繼結點node1處於等待狀態,如果已佔有鎖的執行緒thread1釋放鎖或被CANCEL之後就會通知這個結點node1去獲取鎖執行。

​ ③CONDITION=-2:表示結點在等待佇列中(這裡指的是等待在某個lock的condition上,關於Condition的原理下面會寫到),當持有鎖的執行緒呼叫了Condition的signal()方法之後,結點會從該condition的等待佇列轉移到該lock的同步佇列上,去競爭lock。(注意:這裡的同步佇列就是我們說的AQS維護的FIFO佇列,等待佇列則是每個condition關聯的佇列)

​ ④PROPAGTE=-3:表示下一次共享狀態獲取將會傳遞給後繼結點獲取這個共享同步狀態。

(3)AQS中維持了一個單一的volatile修飾的狀態資訊state(AQS通過Unsafe的相關方法,以原子性的方式由執行緒去獲取這個state)。AQS提供了getState()、setState()、compareAndSetState()函式修改值(實際上呼叫的是unsafe的compareAndSwapInt方法)。下面是AQS中的部分成員變數以及更新state的方法

//這就是我們剛剛說到的head結點,懶載入的(只有競爭失敗需要構建同步佇列的時候,才會建立這個head),如果頭節點存在,它的waitStatus不能為CANCELLED
private transient volatile Node head;
//當前同步佇列尾節點的引用,也是懶載入的,只有呼叫enq方法的時候會新增一個新的wait node
private transient volatile Node tail;
//AQS核心:同步狀態
private volatile int state;
protected final int getState() {
    return state;
}
protected final void setState(int newState) {
    state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

​ (4)AQS的設計師基於模板方法模式的。使用時候需要繼承同步器並重寫指定的方法,並且通常將子類推薦為定義同步元件的靜態內部類,子類重寫這些方法之後,AQS工作時使用的是提供的模板方法,在這些模板方法中呼叫子類重寫的方法。其中子類可以重寫的方法:

//獨佔式的獲取同步狀態,實現該方法需要查詢當前狀態並判斷同步狀態是否符合預期,然後再進行CAS設定同步狀態
protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException();}
//獨佔式的釋放同步狀態,等待獲取同步狀態的執行緒可以有機會獲取同步狀態
protected boolean tryRelease(int arg) { throw new UnsupportedOperationException();}
//共享式的獲取同步狀態
protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException();}
//嘗試將狀態設定為以共享模式釋放同步狀態。 該方法總是由執行釋放的執行緒呼叫。 
protected int tryReleaseShared(int arg) { throw new UnsupportedOperationException(); }
//當前同步器是否在獨佔模式下被執行緒佔用,一般該方法表示是否被當前執行緒所獨佔
protected int isHeldExclusively(int arg) {  throw new UnsupportedOperationException();}

(5)AQS的內部類ConditionObject是通過結合鎖實現執行緒同步,ConditionObject可以直接訪問AQS的變數(state、queue),ConditionObject是個條件變數 ,每個ConditionObject對應一個佇列用來存放執行緒呼叫condition條件變數的await方法之後被阻塞的執行緒。

AQS中的獨佔模式

​ 上面我們簡單瞭解了一下AQS的基本組成,這裡通過ReentrantLock的非公平鎖實現來具體分析AQS的獨佔模式的加鎖和釋放鎖的過程。

非公平鎖的加鎖流程

​ 簡單說來,AQS會把所有的請求執行緒構成一個CLH佇列,當一個執行緒執行完畢(lock.unlock())時會啟用自己的後繼節點,但正在執行的執行緒並不在佇列中,而那些等待執行的執行緒全部處於阻塞狀態(park())。如下圖所示。

​ (1)假設這個時候在初始情況下,還沒有多工來請求競爭這個state,這時候如果第一個執行緒thread1呼叫了lock方法請求獲得鎖,首先會通過CAS的方式將state更新為1,表示自己thread1獲得了鎖,並將獨佔鎖的執行緒持有者設定為thread1。

final void lock() {
    if (compareAndSetState(0, 1))
        //setExclusiveOwnerThread是AbstractOwnableSynchronizer的方法,AQS繼承了AbstractOwnableSynchronizer
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

​ (2)這個時候有另一個執行緒thread2來嘗試或者鎖,同樣也呼叫lock方法,嘗試通過CAS的方式將state更新為1,但是由於之前已經有執行緒持有了state,所以thread2這一步CAS失敗(前面的thread1已經獲取state並且沒有釋放),就會呼叫acquire(1)方法(該方法是AQS提供的模板方法,它會呼叫子類的tryAcquire方法)。非公平鎖的實現中,AQS的模板方法acquire(1)就會呼叫NofairSync的tryAcquire方法,而tryAcquire方法又呼叫的Sync的nonfairTryAcquire方法,所以我們看看nonfairTryAcquire的流程。

//NofairSync
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
    //(1)獲取當前執行緒
    final Thread current = Thread.currentThread();
    //(2)獲得當前同步狀態state
    int c = getState();
    //(3)如果state==0,表示沒有執行緒獲取
    if (c == 0) {
        //(3-1)那麼就嘗試以CAS的方式更新state的值
        if (compareAndSetState(0, acquires)) {
            //(3-2)如果更新成功,就設定當前獨佔模式下同步狀態的持有者為當前執行緒
            setExclusiveOwnerThread(current);
            //(3-3)獲得成功之後,返回true
            return true;
        }
    }
    //(4)這裡是重入鎖的邏輯
    else if (current == getExclusiveOwnerThread()) {
        //(4-1)判斷當前佔有state的執行緒就是當前來再次獲取state的執行緒之後,就計算重入後的state
        int nextc = c + acquires;
        //(4-2)這裡是風險處理
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        //(4-3)通過setState無條件的設定state的值,(因為這裡也只有一個執行緒操作state的值,即
        //已經獲取到的執行緒,所以沒有進行CAS操作)
        setState(nextc);
        return true;
    }
    //(5)沒有獲得state,也不是重入,就返回false
    return false;
}

總結來說就是:

1、獲取當前將要去獲取鎖的執行緒thread2。

2、獲取當前AQS的state的值。如果此時state的值是0,那麼我們就通過CAS操作獲取鎖,然後設定AQS的執行緒佔有者為thread2。很明顯,在當前的這個執行情況下,state的值是1不是0,因為我們的thread1還沒有釋放鎖。所以CAS失敗,後面第3步的重入邏輯也不會進行

3、如果當前將要去獲取鎖的執行緒等於此時AQS的exclusiveOwnerThread的執行緒,則此時將state的值加1,這是重入鎖的實現方式。

4、最終thread2執行到這裡會返回false。

​ (3)上面的thread2加鎖失敗,返回false。那麼根據開始我們講到的AQS概述就應該將thread2構造為一個Node結點加入同步佇列中。因為NofairSync的tryAcquire方法是由AQS的模板方法acquire()來呼叫的,那麼我們看看該方法的原始碼以及執行流程。

//(1)tryAcquire,這裡thread2執行返回了false,那麼就會執行addWaiter將當前執行緒構造為一個結點加入同步佇列中
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

​ 那麼我們就看一下addWaiter方法的執行流程。

private Node addWaiter(Node mode) {
    //(1)將當前執行緒以及阻塞原因(是因為SHARED模式獲取state失敗還是EXCLUSIVE獲取失敗)構造為Node結點
    Node node = new Node(Thread.currentThread(), mode);
    //(2)這一步是快速將當前執行緒插入佇列尾部
    Node pred = tail;
    if (pred != null) {
        //(2-1)將構造後的node結點的前驅結點設定為tail
        node.prev = pred;
        //(2-2)以CAS的方式設定當前的node結點為tail結點
        if (compareAndSetTail(pred, node)) {
            //(2-3)CAS設定成功,就將原來的tail的next結點設定為當前的node結點。這樣這個雙向隊
            //列就更新完成了
            pred.next = node;
            return node;
        }
    }
    //(3)執行到這裡,說明要麼當前佇列為null,要麼存在多個執行緒競爭失敗都去將自己設定為tail結點,
    //那麼就會有執行緒在上面(2-2)的CAS設定中失敗,就會到這裡呼叫enq方法
    enq(node);
    return node;
}

​ 那麼總結一下add Waiter方法

​ 1、將當前將要去獲取鎖的執行緒也就是thread2和獨佔模式封裝為一個node物件。

​ 2、嘗試快速的將當前執行緒構造的node結點新增作為tail結點(這裡就是直接獲取當前tail,然後將node的前驅結點設定為tail),並且以CAS的方式將node設定為tail結點(CAS成功後將原tail的next設定為node,然後這個佇列更新成功)。

​ 3、如果2設定失敗,就進入enq方法。

​ 在剛剛的thread1和thread2的環境下,開始時候執行緒阻塞佇列是空的(因為thread1獲取了鎖,thread2也是剛剛來請求鎖,所以執行緒阻塞佇列裡面是空的)。很明顯,這個時候佇列的尾部tail節點也是null,那麼將直接進入到enq方法。所以我們看看enq方法的實現

private Node enq(final Node node) {
    for (;;) {
        //(4)還是先獲取當前佇列的tail結點
        Node t = tail;
        //(5)如果tail為null,表示當前同步佇列為null,就必須初始化這個同步佇列的head和tail(建
        //立一個哨兵結點)
        if (t == null) { 
            //(5-1)初始情況下,多個執行緒競爭失敗,在檢查的時候都發現沒有哨兵結點,所以需要CAS的
            //設定哨兵結點
            if (compareAndSetHead(new Node()))
                tail = head;
        } 
        //(6)tail不為null
        else {
            //(6-1)直接將當前結點的前驅結點設定為tail結點
            node.prev = t;
            //(6-2)前驅結點設定完畢之後,還需要以CAS的方式將自己設定為tail結點,如果設定失敗,
            //就會重新進入迴圈判斷一遍
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

​ enq方法內部是一個自旋迴圈,第一次迴圈預設情況如下圖所示

​ 1、首先程式碼塊(4)處將t指向了tail,判斷得到t==null,如圖(1)所示;

​ 2、於是需要新建一個哨兵結點作為整個同步佇列的頭節點(程式碼塊5-1處執行)

​ 3、完了之後如圖(2)所示。這樣第一次迴圈執行完畢。

​ 第二次迴圈整體執行如下圖所示。

​ 1、還是先獲取當前tail結點然後將t指向tail結點。如下圖的(3)

​ 2、然後判斷得到當前t!=null,所以enq方法中進入程式碼塊(6).

​ 3、在(6-1)程式碼塊中將node的前驅結點設定為原來佇列的tail結點,如下圖的(4)所示。

​ 4、設定完前驅結點之後,程式碼塊(6-2)會以CAS的方式將當前的node結點設定為tail結點,如果設定成功,就會是下圖(5)所示。更新完tail結點之後,需要保證雙向佇列的,所以將原來的指向哨兵結點的t的next結點指向node結點,如下圖(6)所示。最後返回。

​ 總結來說,即使在多執行緒情況下,enq方法還是能夠保證每個執行緒結點會被安全的新增到同步佇列中,因為enq通過CAS方式將結點新增到同步佇列之後才會返回,否則就會不斷嘗試新增(這樣實際上就是在併發情況下,把向同步佇列新增Node變得序列化了)

​ (4)在上面AQS的模板方法中,acquire()方法還有一步acquireQueued,這個方法的主要作用就是在同步佇列中嗅探到自己的前驅結點,如果前驅結點是頭節點的話就會嘗試取獲取同步狀態,否則會先設定自己的waitStatus為-1,然後呼叫LockSupport的方法park自己。具體的實現如下面程式碼所示

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        //在這樣一個迴圈中嘗試tryAcquire同步狀態
        for (;;) {
            //獲取前驅結點
            final Node p = node.predecessor();
            //(1)如果前驅結點是頭節點,就嘗試取獲取同步狀態,這裡的tryAcquire方法相當於還是調
            //用NofairSync的tryAcquire方法,在上面已經說過
            if (p == head && tryAcquire(arg)) {
                //如果前驅結點是頭節點並且tryAcquire返回true,那麼就重新設定頭節點為node
                setHead(node);
                p.next = null; //將原來的頭節點的next設定為null,交由GC去回收它
                failed = false;
                return interrupted;
            }
            //(2)如果不是頭節點,或者雖然前驅結點是頭節點但是嘗試獲取同步狀態失敗就會將node結點
            //的waitStatus設定為-1(SIGNAL),並且park自己,等待前驅結點的喚醒。至於喚醒的細節
            //下面會說到
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

​ 在上面的程式碼中我們可以看出,這個方法也是一個自旋迴圈,繼續按照剛剛的thread1和thread2這個情況分析。在enq方法執行完之後,同步佇列的情況大概如下所示。

​ 當前的node結點的前驅結點為head,所以會呼叫tryAcquire()方法去獲得同步狀態。但是由於state被thread1佔有,所以tryAcquire失敗。這裡就是執行acquireQueued方法的程式碼塊(2)了。程式碼塊(2)中首先呼叫了shouldParkAfterFailedAcquire方法,該方法會將同步佇列中node結點的前驅結點的waitStatus為CANCELLED的執行緒移除,並將當前呼叫該方法的執行緒所屬結點自己和他的前驅結點的waitStatus設定為-1(SIGNAL),然後返回。具體方法實現如下所示。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //(1)獲取前驅結點的waitStatus
    int ws = pred.waitStatus;
    //(2)如果前驅結點的waitStatus為SINGNAL,就直接返回true
    if (ws == Node.SIGNAL)
        //前驅結點的狀態為SIGNAL,那麼該結點就能夠安全的呼叫park方法阻塞自己了。
        return true;
    if (ws > 0) {
        //(3)這裡就是將所有的前驅結點狀態為CANCELLED的都移除
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        //CAS操作將這個前驅節點設定成SIGHNAL。
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

​ 所以shouldParkAfterFailedAcquire方法執行完畢,現在的同步佇列情況大概就是這樣子,即哨兵結點的waitStatus值變為-1。

​ 上面的執行完畢返回到acquireQueued方法的時候,在acquireQueued方法中就會進行第二次迴圈了,但是還是獲取state失敗,而當再次進入shouldParkAfterFailedAcquire方法的時候,當前結點node的前驅結點head的waitStatus已經為-1(SIGNAL)了,就會返回true,然後acquireQueued方法中就會接著執行parkAndCheckInterrupt將自己park阻塞掛起。

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

​ (5)我們梳理一下整個方法呼叫的流程,假設現在又有一個thread3執行緒競爭這個state,那麼這個方法呼叫的流程是什麼樣的呢。

​ ①首先肯定是呼叫ReentrantLock.lock()方法去嘗試加鎖;

​ ②因為是非公平鎖,所以就會轉到呼叫NoFairSync.lock()方法;

​ ③在NoFairSync.lock()方法中,會首先嚐試設定state的值,因為已經被佔有那麼肯定就是失敗的。這時候就會呼叫AQS的模板方法AQS.acquire(1)。

​ ④在AQS的模板方法acquire(1)中,實際首先會呼叫的是子類的tryAcquire()方法,而在非公平鎖的實現中即Sync.nofairTryAcquire()方法。

​ ⑤顯然tryAcquire()會返回false,所以acquire()繼續執行,即呼叫AQS.addWaiter(),就會將當前執行緒構造稱為一個Node結點,初始狀況下waitStatus為0。

​ ⑥在addWaiter方法中,會首先嚐試直接將構建的node結點以CAS的方式(存在多個執行緒嘗試將自己設定為tail)設定為tail結點,如果設定成功就直接返回,失敗的話就會進入一個自旋迴圈的過程。即呼叫enq()方法。最終保證自己成功被新增到同步佇列中。

​ ⑦加入同步佇列之後,就需要將自己掛起或者嗅探自己的前驅結點是否為頭結點以便嘗試獲取同步狀態。即呼叫acquireQueued()方法。

​ ⑧在這裡thread3的前驅結點不是head結點,所以就直接呼叫shouldParkAfterFailedAcquire()方法,該方法首先會將剛剛的thread2執行緒結點中的waitStatue的值改變為-1(初始的時候是沒有改變這個waitStatus的,每個新節點的新增就會改變前驅結點的waitStatus值)。

​ ⑨thread2所在結點的waitStatus改變後,shouldParkAfterFailedAcquire方法會返回false。所以之後還會在acquireQueued中進行第二次迴圈。並再次呼叫shouldParkAfterFailedAcquire方法,然後返回true。最終呼叫parkAndCheckInterrupt()將自己掛起。

​ 每個執行緒去競爭這個同步狀態失敗的話大概就會經歷上面的這些過程。假設現在thread3經歷上面這些過程之後也進入同步佇列,那麼整個同步佇列大概就是下面這樣了.

​ 將上面的流程整理一下大概就是下面這個圖

非公平鎖的釋放流程

​ 上面說一ReentrantLock為例到了怎樣去獲得非公平鎖,那麼thread1獲取鎖,執行完釋放鎖的流程是怎樣的呢。首先肯定是在finally中呼叫ReentrantLock.unlock()方法,所以我們就從這個方法開始看起。

​ (1)從下面的unlock方法中我們可以看出,實際上是呼叫AQS的release()方法,其中傳遞的引數為1,表示每一次呼叫unlock方法都是釋放所獲得的一次state。重入的情況下會多次呼叫unlock方法,也保證了lock和unlock是成對的。

public void unlock() {
    sync.release(1); //這裡ReentrantLock的unlock方法呼叫了AQS的release方法
}
public final boolean release(int arg) {
    //這裡呼叫了子類的tryRelease方法,即ReentrantLock的內部類Sync的tryRelease方法
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

​ (2)上面看到release方法首先會呼叫ReentrantLock的內部類Sync的tryRelease方法。而通過下面程式碼的分析,大概知道tryRelease做了這些事情。

​ ①獲取當前AQS的state,並減去1;

​ ②判斷當前執行緒是否等於AQS的exclusiveOwnerThread,如果不是,就拋IllegalMonitorStateException異常,這就保證了加鎖和釋放鎖必須是同一個執行緒;

​ ③如果(state-1)的結果不為0,說明鎖被重入了,需要多次unlock,這也是lock和unlock成對的原因;

​ ④如果(state-1)等於0,我們就將AQS的ExclusiveOwnerThread設定為null;

​ ⑤如果上述操作成功了,也就是tryRelase方法返回了true;返回false表示需要多次unlock。

protected final boolean tryRelease(int releases) {
    //(1)獲取當前的state,然後減1,得到要更新的state
    int c = getState() - releases;
    //(2)判斷當前呼叫的執行緒是不是持有鎖的執行緒,如果不是丟擲IllegalMonitorStateException
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //(3)判斷更新後的state是不是0
    if (c == 0) {
        free = true;
        //(3-1)將當前鎖持者設為null
        setExclusiveOwnerThread(null);
    }
    //(4)設定當前state=c=getState()-releases
    setState(c);
    //(5)只有state==0,才會返回true
    return free;
}

​ (3)那麼當tryRelease返回true之後,就會執行release方法中if語句塊中的內容。從上面我們看到,

if (tryRelease(arg)) {
    //(1)獲取當前佇列的頭節點head
    Node h = head;
    //(2)判斷頭節點不為null,並且頭結點的waitStatus不為0(CACCELLED)
    if (h != null && h.waitStatus != 0)
        //(3-1)呼叫下面的方法喚醒同步佇列head結點的後繼結點中的執行緒
        unparkSuccessor(h);
    return true;
}

​ (4)在獲取鎖的流程分析中,我們知道當前同步佇列如下所示,所以判斷得到head!=null並且head的waitStatus=-1。所以會執行unparkSuccessor方法,傳遞的引數為指向head的一個引用h.那下面我們就看看unparkSuccessor方法中處理了什麼事情。

private void unparkSuccessor(Node node) {
    //(1)獲得node的waitStatus
    int ws = node.waitStatus;
    //(2)判斷waitStatus是否小於0
    if (ws < 0)
        //(2-1)如果waitStatus小於0需要將其以CAS的方式設定為0
        compareAndSetWaitStatus(node, ws, 0);

    //(2)獲得s的後繼結點,這裡即head的後繼結點
    Node s = node.next;
    //(3)判斷後繼結點是否已經被移除,或者其waitStatus==CANCELLED
    if (s == null || s.waitStatus > 0) {
        //(3-1)如果s!=null,但是其waitStatus=CANCELLED需要將其設定為null
        s = null;
        //(3-2)會從尾部結點開始尋找,找到離head最近的不為null並且node.waitStatus的結點
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    //(4)node.next!=null或者找到的一個離head最近的結點不為null
    if (s != null)
        //(4-1)喚醒這個結點中的執行緒
        LockSupport.unpark(s.thread);
}

​ 從上面的程式碼實現中可以總結,unparkSuccessor主要做了兩件事情:

​ ①獲取head節點的waitStatus,如果小於0,就通過CAS操作將head節點的waitStatus修改為0

​ ②尋找head節點的下一個節點,如果這個節點的waitStatus小於0,就喚醒這個節點,否則遍歷下去,找到第一個waitStatus<=0的節點,並喚醒。

​ (5)下面我們應該分析的是釋放掉state之後,喚醒同步佇列中的結點之後程式又是是怎樣執行的。按照上面的同步佇列示意圖,那麼下面會執行這些

​ ①thread1(獲取到鎖的執行緒)呼叫unlock方法之後,最終執行到unparkSuccessor方法會喚醒thread2結點。所以thread2被unpark。

​ ②再回想一下,當時thread2是在呼叫acquireQueued方法之後的parkAndCheckInterrupt裡面被park阻塞掛起了,所以thread2被喚醒之後繼續執行acquireQueued方法中的for迴圈(到這裡可以往前回憶看一下acquireQueued方法中的for迴圈做了哪些事情);

​ ③for迴圈中做的第一件事情就是檢視自己的前驅結點是不是頭結點(按照上面的同步佇列情況是滿足的);

​ ④前驅結點是head結點,就會呼叫tryAcquire方法嘗試獲取state,因為thread1已經釋放了state,即state=0,所以thread2呼叫tryAcquire方法時候,以CAS的方式去將state從0更新為1是成功的,所以這個時候thread2就獲取到了鎖

​ ⑤thread2獲取state成功,就會從acquireQueued方法中退出。注意這時候的acquireQueued返回值為false,所以在AQS的模板方法的acquire中會直接從if條件退出,最後執行自己鎖住的程式碼塊中的程式