深入理解ReentrantLock原理&&Condition原理

ReentrantLock 介紹

一個可重入的互斥鎖,它具有與使用{synchronized}方法和語句訪問的隱式監視器鎖相同的基本行爲和語義,但它具有可擴展的能力。

一個ReentrantLock會被最後一次成功鎖定(lock)的線程擁有,在還沒解鎖(unlock)之前。當鎖沒有被其他線程擁有的話,一個線程執行『lock』方法將會返回,獲取鎖成功。一個方法將會立即的返回,如果當前線程已經擁有了這個鎖。可以使用『isHeldByCurrentThread』和『getHoldCount』來檢查當前線程是否持有鎖。

這個類的構造方法會接受一個可選的“fairness”參數。當該參數設置爲true時,在發生多線程競爭時,鎖更傾向將使用權授予給最長等待時間的線程。另外,鎖不保證任何特定的訪問順序。程序在多線程情況下使用公平鎖來訪問的話可能表現出較低的吞吐量(如,較慢;經常慢很多)與比使用默認設置相比,但是在獲取鎖上有較小的時間差異,並保證不會有飢餓(線程)。然而需要注意的是,公平鎖並不保證線程調度的公平性。(也就是說,即使使用公平鎖,也無法確保線程調度器是公平的。如果線程調度器選擇忽略一個線程,而該線程爲了這個鎖已經等待了很長時間,那麼就沒有機會公平地處理這個鎖了)
還需要注意的是,沒有時間參數的『tryLock()』方法是沒有信譽的公平設置。它將會成功如果鎖是可獲取的,即便有其他線程正在等待獲取鎖。

除了對Lock接口的實現外,這個類還定義了一系列的public和protected方法用於檢測lock的state。這些方法中的某些方法僅用於檢測和監控。

這個類的序列化行爲同lock內置的行爲是一樣的:一個反序列化的鎖的狀態(state)是未鎖定的(unlocked),無論它序列化時的狀態(state)是什麼。

這個鎖支持同一個線程最大遞歸獲取鎖2147483647(即,Integer.MAX_VALUE)次。如果嘗試獲取鎖的次數操作了這個限制,那麼一個Error獲取lock方法中拋出。

AbstractQueuedSynchronizer

ReentrantLock的公平鎖和非公平鎖都是基於AbstractQueuedSynchronizer(AQS)實現的。ReentrantLock使用的是AQS的排他鎖模式,由於AQS除了排他鎖模式還有共享鎖模式,本文僅對ReentrantLock涉及到的排他鎖模式部分的內容進行介紹,關於共享鎖模式的部分會在 深入理解CountDownLatch原理一文中介紹。

AQS提供一個框架用於實現依賴於先進先出(FIFO)等待隊列的阻塞鎖和同步器(信號量,事件等)。這個類被設計與作爲一個有用的基類,一個依賴單一原子值爲代表狀態的多種同步器的基類。子類必須將修改這個狀態值的方法定義爲受保護的方法,並且該方法會根據對象(即,AbstractQueuedSynchronizer子類)被獲取和釋放的方式來定義這個狀態。根據這些,這個類的其他方法實現所有排隊和阻塞的機制。子類能夠維護其他的狀態屬性,但是隻有使用『getState』方法、『setState』方法以及『compareAndSetState』方法來原子性的修改 int 狀態值的操作才能遵循相關同步性。

等待隊列節點類 ——— Node

等待隊列是一個CLH鎖隊列的變體。CLH通常被用於自旋鎖(CLH鎖是一種基於鏈表的可擴展、高性能、公平的自旋鎖,申請線程只在本地變量上自旋,它不斷輪詢前驅的狀態,如果發現前驅釋放了鎖就結束自旋。)。我們用它來代替阻塞同步器,但是使用相同的基本策略,該策略是持有一些關於一個線程在它前驅節點的控制信息。一個“status”字段在每個節點中用於保持追蹤是否一個線程需要被阻塞。一個節點會得到通知當它的前驅節點被釋放時。隊列中的每一個節點都作爲一個持有單一等待線程的特定通知風格的監視器。狀態字段不會控制線程是否被授予鎖等。一個線程可能嘗試去獲取鎖如果它在隊列的第一個。但是首先這並不保證成功,它只是給與了競爭的權力(也就是說,隊列中第一個線程嘗試獲取鎖時,並不保證一定能得到鎖,它只是有競爭鎖的權力而已)。所以當前被釋放的競爭者線程可能需要重新等待獲取鎖。
(這裏說的"隊列中的第一個的線程"指的時,從隊列頭開始往下的節點中,第一個node.thread != null的線程。因爲,AQS隊列的head節點是一個虛節點,不是有個有效的等待節點,因此head節點的thread是爲null的。)

爲了排隊進入一個CLH鎖,你可以原子性的拼接節點到隊列中作爲一個新的隊尾;對於出隊,你只要設置頭字段。(即,入隊操作時新的節點會排在CLH鎖隊列的隊尾,而出隊操作就是將待出隊的node設置爲head。由此可見,在AQS中維護的這個等待隊列,head是一個無效的節點。初始化時head是一個new Node()節點;在後期的操作中,需要出隊的節點就會設置到head中。)

          +------+  prev +-----+       +-----+
     head |      | <---- |     | <---- |     |  tail
          +------+       +-----+       +-----+

插入到一個CLH隊列的請求只是一個對“tail”的單個原子操作,所以有一個簡單的從未入隊到入隊的原子分割點。類似的,出隊調用只需要修改“head”。然而,節點需要更多的工作來確定他們的後繼者是誰,部分是爲了處理由於超時和中斷而導致的可能的取消。
(也就是說,一個node的後繼節點不一定就是node.next,因爲隊列中的節點可能因爲超時或中斷而取消了,而這些取消的節點此時還沒被移除隊列(也許正在移除隊列的過程中),而一個node的後繼節點指的是一個未被取消的有效節點,因此在下面的操作中你就會發現,在尋找後繼節點時,尋找的都是當前節點後面第一個有效節點,即非取消節點。)

“prev”(前驅)連接(原始的CLH鎖是不使用前驅連接的),主要用於處理取消。如果一個節點被取消了,它的後驅(通常)會重連接到一個未被取消的前驅。

另外我們使用“next”連接去實現阻塞機制。每個節點的線程ID被它們自己的節點所持有,所以前驅節點通知下一個節點可以被喚醒,這是通過遍歷下一個鏈接(即,next字段)來確定需要喚醒的線程。後繼節點的決定必須同‘新入隊的節點在設置它的前驅節點的“next”屬性操作(即,新入隊節點爲newNode,在newNode的前驅節點preNewNode進行preNewNode.next = newNode操作)’產生競爭。一個解決方法是必要的話當一個節點的後繼看起來是空的時候,從原子更新“tail”向前檢測。(或者換句話說,next鏈接是一個優化,所以我們通常不需要反向掃描。)

取消引入了對基本算法的一些保守性。當我們必須爲其他節點的取消輪詢時,我們不需要留意一個取消的節點是在我們節點的前面還是後面。它的處理方式是總是根據取消的節點喚醒其後繼節點,允許它們去連接到一個新的前驅節點,除非我們能夠標識一個未被取消的前驅節點來完成這個責任。

  • waitStatus
volatile int waitStatus;

狀態屬性,只有如下值:
① SIGNAL:
static final int SIGNAL = -1;
這個節點的後繼(或者即將被阻塞)被阻塞(通過park阻塞)了,所以當前節點需要喚醒它的後繼當它被釋放或者取消時。爲了避免競爭,獲取方法必須首先表示他們需要一個通知信號,然後再原子性的嘗試獲取鎖,如果失敗,則阻塞。
也就是說,在獲取鎖的操作中,需要確保當前node的preNode的waitStatus狀態值爲’SIGNAL’,纔可以被阻塞,當獲取鎖失敗時。(『shouldParkAfterFailedAcquire』方法的用意就是這)
② CANCELLED:
static final int CANCELLED = 1;
這個節點由於超時或中斷被取消了。節點不會離開(改變)這個狀態。尤其,一個被取消的線程不再會被阻塞了。
③ CONDITION:
static final int CONDITION = -2;
這個節點當前在一個條件隊列中。它將不會被用於當做一個同步隊列的節點直到它被轉移到同步隊列中,轉移的同時狀態值(waitStatus)將會被設置爲0。(這裏使用這個值將不會做任何事情與該字段其他值對比,只是爲了簡化機制)。
④ PROPAGATE:
static final int PROPAGATE = -3;
一個releaseShared操作必須被廣播給其他節點。(只有頭節點的)該值會在doReleaseShared方法中被設置去確保持續的廣播,即便其他操作的介入。
⑤ 0:不是上面的值的情況。
這個值使用數值排列以簡化使用。非負的值表示該節點不需要信號(通知)。因此,大部分代碼不需要去檢查這個特殊的值,只是爲了標識。
對於常規的節點該字段會被初始化爲0,競爭節點該值爲CONDITION。這個值使用CAS修改(或者可能的話,無競爭的volatile寫)。

  • prev
volatile Node prev

連接到前驅節點,當前節點/線程依賴與這個節點waitStatus的檢測。分配發生在入隊時,並在出隊時清空(爲了GC)。並且,一個前驅的取消,我們將短路當發現一個未被取消的節點時,未被取消的節點總是存在因爲頭節點不能被取消:只有在獲取鎖操作成功的情況下一個節點纔會成爲頭節點。一個被取消的線程絕不會獲取成功,一個線程只能被它自己取消,不能被其他線程取消。

  • next
volatile Node next

連接到後繼的節點,該節點是當前的節點/線程釋放喚醒的節點。分配發生在入隊時,在繞過取消的前驅節點時進行調整,並在出隊列時清空(爲了GC的緣故)。一個入隊操作(enq)不會被分配到前驅節點的next字段,直到tail成功指向當前節點之後(通過CAS來將tail指向當前節點。『enq』方法實現中,會先將node.prev = oldTailNode;在需要在CAS成功之後,即tail = node之後,再將oldTailNode.next = node;),所以當看到next字段爲null時並不意味着當前節點是隊列的尾部了。無論如何,如果一個next字段顯示爲null,我們能夠從隊列尾向前掃描進行復核。被取消的節點的next字段會被設置爲它自己,而不是一個null,這使得isOnSyncQueue方法更簡單。

  • thread
volatile Thread thread

這個節點的入隊線程。在構建時初始化,在使用完後清除。

  • nextWaiter
Node nextWaiter

鏈接下一個等待條件的節點,或者一個指定的SHARED值。因爲只有持有排他鎖時能訪問條件隊列,所以我們只需要一個簡單的單鏈表來維持正在等待條件的節點。它們接下來會被轉換到隊列中以去重新獲取鎖。因爲只有排他鎖纔有conditions,所以我們使用給一個特殊值保存的字段來表示共享模式。
也就是說,nextWaiter用於在排他鎖模式下表示正在等待條件的下一個節點,因爲只有排他鎖模式有conditions;所以在共享鎖模式下,我們使用’SHARED’這個特殊值來表示該字段。

源碼分析

初始化
  • 初始化 ———— 公平鎖:
ReentrantLock lock = new ReentrantLock(true)
  • 初始化 ———— 非公平鎖:
ReentrantLock lock = new ReentrantLock()

ReentrantLock lock = new ReentrantLock(false)


lock
public void lock() {
    sync.lock();
}

獲取鎖。
如果其他線程沒有持有鎖的話,獲取鎖並且立即返回,設置鎖被持有的次數爲1.
如果當前線程已經持有鎖了,那麼只有鎖的次數加1,並且方法立即返回。
如果其他線程持有了鎖,那麼當前線程會由於線程調度變得不可用,並處於休眠狀態直到當前線程獲取到鎖,此時當前線程持有鎖的次數被設置爲1次。

  • 公平鎖『lock()』方法的實現:
    『FairSync#lock()』
final void lock() {
    acquire(1);
}

調用『acquire』在再次嘗試獲取鎖失敗的情況下,會將當前線程入隊至等待隊列。該方法會在成功獲取鎖的情況下才會返回。因此該方法是可能導致阻塞的(線程掛起)。

  • 非公平鎖『lock()』方法的實現:
    『NonfairSync#lock()』
final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

① 嘗試獲取鎖,若『compareAndSetState(0, 1)』操作成功(這步操作有兩層意思。第一,當前state爲0,說明當前鎖沒有被任何線程持有;第二,原子性的將state從’0’修改爲’1’成功,說明當前線程成功獲取了這個鎖),則說明當前線程成功獲取鎖。那麼設置鎖的持有者爲當前線程(『setExclusiveOwnerThread(Thread.currentThread())』)。
那麼此時,AQS state爲1,鎖的owner爲當前線程。結束方法。
② 如果獲取鎖失敗,則調用『acquire』在再次嘗試獲取鎖失敗的情況下,會將當前線程入隊至等待隊列。該方法會在成功獲取鎖的情況下才會返回。因此該方法是可能導致阻塞的(線程掛起)。

  • 公共方法『AbstractQueuedSynchronizer#acquire』
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

在排他模式下嘗試獲,忽略中斷。該方法的實現至少執行一次『tryAcquire』方法,如果成功獲取鎖則返回。否則,線程將入隊等待隊列中,可能會重複阻塞和解除阻塞,以及調用『tryAcquire』直到成功獲取鎖。這個方法能被用於實現『Lock#lock』
① 執行『tryAcquire』來嘗試獲取鎖,如果成功(即,返回true)。則返回true退出方法;否則到第②步
② 執行『acquireQueued(addWaiter(Node.EXCLUSIVE), arg)』
『addWaiter(Node.EXCLUSIVE)』:爲當前線程創建一個排他模式的Node,並將這個節點加入等待隊列尾部。
『acquireQueued』:已經入隊的節點排隊等待獲取鎖。
③ 如果在嘗試獲取鎖的過程中發現線程被標誌位了中斷。因爲是通過『Thread.interrupted()』方法來檢測的當前線程是否有被標誌位中斷,該方法會清除中斷標誌,所以如果線程在嘗試獲取鎖的過程中發現被標識爲了中斷,則需要重新調用『Thread.currentThread().interrupt();』重新將中斷標誌置位。
該方法是排他模式下獲取鎖的方法,並且該方法忽略中斷,也就說中斷不會導致該方法的結束。首先,會嘗試通過不公平的方式立即搶佔該鎖(『tryAcquire』),如果獲取鎖成功,則結束方法。否則,將當前線程加入到等待獲取鎖的隊列中,如果當前線程還未入隊的話。此後就需要在隊列中排隊獲取鎖了,而這就不同於前面非公平的方式了,它會根據FIFO的公平方式來嘗試獲取這個鎖。而這個方法會一直“阻塞”直到成功獲取到鎖了纔會返回。注意,這裏的“阻塞”並不是指線程一直被掛起這,它可能被喚醒,然後同其他線程(比如,那麼嘗試非公平獲取該鎖的線程)競爭這個鎖,如果失敗,它會繼續被掛起,等待被喚醒,再重新嘗試獲取鎖,直到成功。
同時注意,關於中斷的操作。因爲該方法是不可中斷的方法,因此若在該方法的執行過程中線程被標誌位了中斷,我們需要確保這個標誌位不會因爲方法的調用而被清除,也就是我們不處理中斷,但是外層的邏輯可能會對中斷做相關的處理,我們不應該影響中斷的狀態,即,“私自”在不處理中斷的情況下將中斷標誌清除了。


先繼續來看公平鎖和非公平鎖對『tryAcquire』方法的實現
tryAcquire 這類型的方法都不會導致阻塞(即,線程掛起)。它會嘗試獲取鎖,如果失敗就返回false。

  • FairSync#tryAcquire
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

公平版本的『tryAcquire』方法。當前線程沒有權限獲取鎖,除非遞歸調用到沒有等待者了,或者當前線程就是第一個嘗試獲取鎖的線程(即,等待隊列中沒有等待獲取鎖的線程)。
① 獲取AQS state,即,當前鎖被獲取的次數。如果爲’0’,則說明當前鎖沒有被任何線程獲取,那麼執行『hasQueuedPredecessors』方法判斷當前線程之前是否有比它等待更久準備獲取鎖的線程:
a)如果有則方法結束,返回false;
b)如果沒有,則說明當前線程前面沒有另一個比它等待更久的時間在等待獲取這個鎖的線程,則嘗試通過CAS的方式讓當前的線程獲取鎖。如果成功則設置持有鎖的線程爲當前線程(『setExclusiveOwnerThread(current)』),然後方法結束返回true。
② 如果AQS state > 0,則說明當前鎖已經被某個線程所持有了,那麼判斷這個持有鎖的線程是否就是當前線程(『current == getExclusiveOwnerThread()』),如果是的話,嘗試進行再次獲取這個鎖(ReentrantLock是一個可重入的鎖)如果獲取鎖的次數沒有超過上限的話(即,c + acquires > 0),則更新state的值爲最終該鎖被當前線程獲取的次數,然後方法結束返回true;否則,如果當前線程獲取這個鎖的次數超過了上限則或拋出Error異常。再者如果當前線程不是持有鎖的線程,則方法結束返回false。


『AbstractQueuedSynchronizer#hasQueuedPredecessors』

public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

查詢是否有線程已經比當前線程等待更長的時間在等待獲取鎖。
這個方法的調用等價於(但更高效與):
『getFirstQueuedThread() != Thread.currentThread() && hasQueuedThreads()』
即,可見如下任一情況,則說明當前線程前面沒有比它等待更久的需要獲取鎖的線程:
a)當隊列中第一個等待獲取鎖的線程是當前線程時
b)等待隊列爲空時。即,當前沒有其他線程等待獲取鎖。
注意,因爲中斷和超時導致的取消可能發生在任何時候,該方法返回‘true’不能保證其他線程會比當前線程更早獲得到鎖。同樣,由於隊列是空的,在當前方法返回‘false’之後,另一個線程可能會贏得一個入隊競爭。
這個方法被涉及用於一個公平的同步器,以避免闖入。如果這個方法返回’true’,那麼像是一個(公平)同步器的『tryAcquire』方法應該返回’false’,以及『tryAcquireShared』方法需要返回一個負數值(除非這是一個可重入的鎖,因爲可重入鎖,獲取鎖的結果還需要判斷當前線程是否就是已經獲取鎖的線程了,如果是,則在沒有超過同一線程可獲取鎖的次數上限的情況下,當前線程可以再次獲取這個鎖)。比如,一個公平的、可重入的、排他模式下的『tryAcquire』方法,可能看起來像是這樣的:

返回:
a)true:如果當前線程前面有排隊等待的線程
b)false:如果當前線程是第一個等待獲取鎖的線程(即,一般就是head.next);或者等待隊列爲空。

該方法的正確性依賴於head在tail之前被初始化,以及head.next的精確性,如果當前線程是隊列中第一個等待獲取鎖的線程的時候。
① tail節點的獲取一定先於head節點的獲取。因爲head節點的初始化在tail節點之前,那麼基於當前的tail值,你一定能獲取到有效的head值。這麼做能保證接下來流程的正確性。舉個反例來說明這麼做的必要性:如果你按『Node h = head; Node t = tail;』的順序來對h、t進行賦值,那麼可能出現你在操作這兩步的時候有其他的線程正在執行隊列的初始化操作,那麼就可能的帶一個『h==null』,而『tail!=null』的情況(這種情況下,是不對的,因爲tail!=null的話,head一定也不爲null了),這使得『h != t』判斷爲true,認爲當下是一個非空的等待隊列,那麼接着執行『s = h.next』就會拋出NPE異常了。但是當『Node t = tail; Node h = head;』按初始化相反的順序來賦值的話,則不會有問題,因爲我們保證了在tail取值的情況下,head的正確性。我們接下看下面的步驟,來說明爲什麼這麼做就可以了。
② 在獲取完t、h之後,我們接着先判斷『h != t』,該判斷的用意在於,判斷當前的隊列是否爲空。如果爲true則說明,當前隊列非空。如果爲false 則說明當前隊列爲空,爲空的話,方法就直接結束了,並返回false。
但是請注意,當『h != t』爲true時,其實是有兩種情況的:
a)當tail和head都非空時,說明此時等待隊列已經完成了初始化,head和tail都指向了其隊列的頭和隊列的尾。
b)當“tail==null”同時“head != null”,則說明,此時隊列正在被其他線程初始化,當前我們獲取的h、t是初始化未完成的中間狀態。但是沒關係,下面的流程會對此請進行判斷。
③ 當『h != t』返回’true’的話,繼續判斷『(s = h.next) == null || s.thread != Thread.currentThread()』。這裏的兩個判斷分別對應了兩種情況:
a)『(s = h.next) == null』返回’true’,則說明當獲取的h、t爲初始化的中間狀態,因爲第一個線程入隊的時候,會先初始化隊列,然後纔對head的next值進行賦值,所以我們需要“s = h.next”是否爲null進行判斷,如果爲’null’,則說明當前等待隊列正在被初始化,並且有一個線程正在入隊的操作中。所以此時方法直接結束,並且返回true。
b)如果『h != t』並且『(s = h.next) != null』,則說明當前線程已經被初始化好了,並且等待隊列中的第一個等待獲取鎖的線程也已經入隊了。那麼接着我們就判斷這個在等待隊列中第一個等待獲取鎖的線程是不是當前線程『s.thread != Thread.currentThread()』,如果是的話,方法結束並返回false,表示當前線程前面沒有比它等待更久獲取這個鎖的線程了;否則方法結束返回true,表示當前線程前面有比它等待更久希望獲取這個鎖的線程。


『AbstractQueuedSynchronizer#addWaiter』

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 pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

根據給定的模式創建當前線程的節點,並將創建好的節點入隊(加入等待隊列尾部)。
首先在隊列非空的情況下會嘗試一次快速入隊,也就是通過嘗試一次CAS操作入隊,如果CAS操作失敗,則調用enq方法進行“自旋+CAS”方法將創建好的節點加入隊列尾。
在排他模式下,將節點加入到鎖的同步隊列時,Node的mode(即,waitStatus)爲’EXCLUSIVE’。waitStatus是用於在排他鎖模式下當節點處於條件隊列時表示下一個等待條件的節點,所以在加入到鎖的同步隊列中(而非條件隊列),我們使用’EXCLUSIVE’這個特殊值來表示該字段。本文主要圍繞共享鎖模式的介紹,就不對其進行展開了,關於排他鎖的內容會在“ReentrantLock源碼解析”一文中介紹。

  • NonfairSync#tryAcquire
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

對父類AQS tryAcquire方法的重寫。調用『nonfairTryAcquire(acquires)』方法,非公平的嘗試獲取這個可重入的排他鎖


『nonfairTryAcquire』

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

執行不公平的『tryLock』。『tryAcquire』在子類中實現,但是都需要不公平的嘗試在『tryLock』方法中。
① 獲取state值,如果爲’0’,則說明當前沒有線程佔用鎖,那麼調用CAS來嘗試將state的值從0修改爲’acquires’的值,
a)如果成功則說明當前線程成功獲取到了這個不公平鎖,那麼通過『setExclusiveOwnerThread(current)』方法來標誌當前線程爲持有鎖的線程,方法結束,返回true;
b)如果失敗,則說明有其他線程先獲取到了這個鎖,那麼當前線程獲取鎖失敗。方法結束,返回false。
② "state != 0",則說明當前鎖已經被某個線程所持有了,那麼判斷當前的線程是否就是持有鎖的那個線程(『if (current == getExclusiveOwnerThread())』)。
a)如果持有鎖的線程就是當前線程,因爲ReentrantLock是一個可重入的鎖,所以接下來繼續判斷預計遞歸獲取鎖的次數是否超過了限制的值(即,“nextc < 0”則說明預計遞歸獲取鎖的次數超過了限制值Integer.MAX_VALUE了),那麼會拋出“Error”異常;否則將當前state的值設置爲最新獲取鎖的次數(注意,這裏不需要使用CAS的方式來修改state了,因爲能操作到這裏的一定就是當前持有鎖的線程了,因此是不會發送多線程競爭的情況)。然後方法結束,返回true;
b)如果持有鎖的線程不是當前線程,那麼當前線程獲取鎖失敗。方法結束,返回false。

  • 『FairSync#lock』 VS 『NonfairSync#lock』
    a)在公平鎖的模式下,所有獲取鎖的線程必須是按照調用lock方法先後順序來決定的,嚴格的說當有多個線程同時嘗試獲取同一個鎖時,多個線程最終獲取鎖的先後順序是由入隊等待隊列的順序決定的,當然,第一個獲取鎖的線程是無需入隊的,等待隊列是用於存儲那些嘗試獲取鎖失敗後的節點。並且按照FIFO的順序讓隊列中的節點依次獲取鎖。
    b)在非公平模式下,當執行lock時,無論當前等待隊列中是否有等待獲取鎖的線程了,當前線程都會嘗試直接去獲取鎖。
    👆兩點從『FairSync#lock』與『NonfairSync#lock』 實現的不同,以及『FairSync#tryAcquire』與『NonfairSync#tryAcquire』方法實現的不同中都能表現出來。

對於非公平鎖:首先會嘗試立即搶佔獲取鎖(若鎖當前沒有被任何線程持有時,並且此時它會和當前同時首次嘗試獲取該鎖的線程以及等待隊列中嘗試獲取該鎖的線程競爭),如果獲取鎖失敗,則會被入隊到等待隊列中,此時就需要排隊等待獲取鎖了。

在排他鎖模式下,等待隊列中的第一個等待獲取鎖的線程(即,前驅節點是head節點的節點),僅代表這個節點當前有競爭獲取鎖的權力,並不代表它會成功獲取鎖。因爲它可能會同非公平獲取鎖的操作產生競爭。


繼續回到『AbstractQueuedSynchronizer#acquire』方法,繼續展開裏面的實現。我們接着看『acquireQueued』方法是如何實現將已經入隊的節點排隊等待獲取鎖的。
『AbstractQueuedSynchronizer#acquireQueued』

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

用於已經入隊的線程在排他不可中斷模式下獲取鎖。同時也被用於給條件(condition)的wait方法來獲取鎖。
該方法不會因爲中斷的發送而返回,只有在獲取鎖的情況下才會返回,但是如果在等待獲取鎖的過程中,當前線程被標識爲了中斷,則在方法返回的時候返回true,否則方法返回是返回false。
① 獲取當前節點的前驅節點,如果前驅節點是head節點,則說明當前節點是隊列中第一個等待獲取鎖的節點,那麼就執行『tryAcquire』方法嘗試獲取排他鎖,如果獲取排他鎖成功(即,tryAcquire方法返回true)則調用『setHead(node)』將當前節點設置爲頭節點。然後將p(即,舊的head節點)的next置null,有助於p被垃圾收集器收集。然後標識failed爲false。結束方法調用,返回interrupted(該字段表示在等待獲取鎖的過程中,當前線程是否有被表示爲中斷了)。
② 如果當前節點的前驅節點不是head節點,說明該節點前面已經有等待着獲取這個排他鎖的節點;或者當前節點的前驅節點是head節點,但是當前節點獲取鎖失敗了,那麼執行『shouldParkAfterFailedAcquire』方法,若該方法返回true,則說明本次獲取排他鎖失敗需要阻塞/掛起當前線程,那麼就調用『LockSupport.park(this);』將當前線程掛起,直到被喚醒,並且若掛起期間該線程被標誌爲了中斷狀態,則將interrupted標識爲true。
③ 噹噹前節點經過多次喚醒與掛起,終於成功獲取鎖後,則退出方法,並返回當前線程是否有被中斷的標誌。如果當前節點因爲某些原因沒有成功獲取到鎖,卻要結束該方法了,那麼調用『cancelAcquire(node)』方法將當前節點從等待隊列中移除。因爲方法結束了,說明當前節點不會被操作再去嘗試獲取鎖了,那麼就不應該作爲一個有效節點放在等待隊列中,應該被標識爲無效的節點後從隊列中移除。


『AbstractQueuedSynchronizer#setHead』

private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}

將node設置爲隊列的頭節點,當它出隊時。該方法只能被獲取方法調用。僅將無用字段清空(即,置爲null)以便於GC並廢除不必要的通知和遞歸。


『AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire』

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    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.
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

檢查並修改一個節點的狀態,當該節點獲取鎖失敗時。返回true如果線程需要阻塞。這是主要的信號通知控制在所有的獲取鎖循環中。要求’pred’ == ‘node.prev’
① 如果pred.waitStatus == Node.SIGNAL。則說明node的前驅節點已經被要求去通知釋放它的後繼節點,所以node可以安全的被掛起(park)。然後,退出方法,返回true。
② 如果pred.waitStatus > 0。則說明node的前驅節點被取消了。那麼跳過這個前驅節點並重新標誌一個有效的前驅節點(即,waitStatus <= 0 的節點可作爲有效的前驅節點),然後,退出方法,返回false。
③ 其他情況下,即pred.waitStatus爲’0’或’PROPAGATE’。表示我們需要一個通知信號(即,當前的node需要喚醒的通知),但是當前還不能掛起node。調用『compareAndSetWaitStatus(pred, ws, Node.SIGNAL)』方法通過CAS的方式來修改前驅節點的waitStatus爲“SIGNAL”。退出方法,返回false。
我們需要一個通知信號,主要是因爲當前線程要被掛起了(park)。而如果waitStatus已經是’SIGNAL’的話就無需修改,直接掛起就好,而如果waitStatus是’CANCELLED’的話,說明prev已經被取消了,是個無效節點了,那麼無需修改這個無效節點的waitStatus,而是需要先找到一個有效的prev。因此,剩下的情況就只有當waitStatus爲’0’和’PROPAGAET’了(注意,waitStatus爲’CONDITION’是節點不在等待隊列中,所以當下情況waitStatus不可能爲’CONDITION’),這是我們需要將prev的waitStatus使用CAS的方式修改爲’SIGNAL’,而且只有修改成功的情況下,當前的線程才能安全被掛起。
還值得注意的時,因此該方法的CAS操作都是沒有自旋的,所以當它操作完CAS後都會返回false,在外層的方法中會使用自旋,當發現返回的是false時,會再次調用該方法,以檢查保證有當前node有一個有效的prev,並且其waitStatus爲’SIGNAL’,在此情況下當前的線程纔會被掛起(park)。

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

嘗試去釋放這個鎖。
如果當前線程是持有這個鎖的線程,那麼將持有次數減少1。如果釋放後當前的鎖被持有的次數爲0,那麼鎖被釋放。如果當前線程不是持有鎖的線程,那麼拋出“IllegalMonitorStateException”異常。


『AbstractQueuedSynchronizer#release』

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

排他模式下的釋放。該方法的實現通過解除一個或多個線程的的阻塞,如果『tryRelase』方法返回true的話。
該方法能夠被用於實現『Lock#unlock』。
① 調用『tryRelease(arg)』方法來嘗試設置狀態值來反映一個排他模式下的釋放。如果操作成功,則進入步驟[2];否則,如果操作失敗,則方法結束,返回false。
② 在成功釋放給定的狀態值後,獲取等待隊列的頭節點。如果頭節點不爲null並且頭節點的waitStatus!=0(頭節點的waitStatus要麼是’0’,要麼是’SIGNAL’),那麼執行『unparkSuccessor(h)』來喚醒頭節點的後繼節點。(節點被喚醒後,會繼續acquireQueued方法中流程)
③ 只要『tryRelease(arg)』釋放操作成功,無論是否需要喚醒頭結點的後繼節點,方法結束都會返回true。


『tryRelease』

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

嘗試通過設置狀態值來反映一個在排他模式下的釋放操作。
①『Thread.currentThread() != getExclusiveOwnerThread()』如果執行釋放操作的線程不是持有鎖的線程,那麼直接拋出“IllegalMonitorStateException”異常,方法結束;否則
② 如果執行當前釋放操作的線程是持有鎖的線程,那麼
a)計算新state的值,即當前釋放操作後state的值,如果state爲0,則說明當前線程完成釋放了對該鎖的持有,那麼將鎖的持有者重置爲null(即,『setExclusiveOwnerThread(null)』)。然後通過『setState(c);』將AQS的state值設置爲這個新的state值(即,0),結束方法,返回true,表示該鎖現在沒有線程持有,可以被重新獲取。
b)如果新state的值不爲0(即,大於0),則說明當前的線程並未完成釋放該鎖(因爲reentrantLock是一個可重入的鎖,所以一個線程可以多次獲取這鎖,而state值就表示這一線程獲取鎖的次數),那麼通過『setState(c);』將AQS的state值設置爲這個新的state值,結束方法,返回false。
可見對於『tryRelease』方法,釋放鎖操作失敗是通過拋出“IllegalMonitorStateException”異常來表示的。該方法無論返回‘true’還是‘false’都表示本次的釋放操作完成了。返回‘true’表示的是鎖已經被當前線程完全釋放了,其他線程可以繼續爭奪這個鎖了,在完全釋放鎖的時候也會將鎖中持有者字段重新置null;返回‘false’表示的是當前釋放操作完成後,該線程還繼續持有這該鎖,此時其他線程是無法獲取到這個鎖的。
同時,我們可以知道,釋放操作只能有持有鎖的線程來完成,因此對於AQS state字段(一個volatile字段)的修改,不需要使用CAS來完成,只需要直接設置修改就好。


『AbstractQueuedSynchronizer#unparkSuccessor』

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;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
<span class="token comment">/*
 * 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.
 */</span>
<span class="token class-name">Node</span> s <span class="token operator">=</span> node<span class="token punctuation">.</span>next<span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>s <span class="token operator">==</span> <span class="token keyword">null</span> <span class="token operator">||</span> s<span class="token punctuation">.</span>waitStatus <span class="token operator">&gt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    s <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token class-name">Node</span> t <span class="token operator">=</span> tail<span class="token punctuation">;</span> t <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> t <span class="token operator">!=</span> node<span class="token punctuation">;</span> t <span class="token operator">=</span> t<span class="token punctuation">.</span>prev<span class="token punctuation">)</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>t<span class="token punctuation">.</span>waitStatus <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span>
            s <span class="token operator">=</span> t<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>s <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
    <span class="token class-name">LockSupport</span><span class="token punctuation">.</span><span class="token function">unpark</span><span class="token punctuation">(</span>s<span class="token punctuation">.</span>thread<span class="token punctuation">)</span><span class="token punctuation">;</span>

}

喚醒後繼節點,如果存在的話
① 如果狀態值是負數,則在預期發信號通知時清除這個負數狀態值。如果狀態被等待的線程修改了或者清除負數狀態值失敗是允許。
② 後繼節點的線程被喚醒,後繼節點通常就是下一個節點。但是如果下一個節點被取消了或者下一個節點爲null,則從隊列尾(tail)往前遍歷去找真實的未取消的後繼節點。
『(s == null || s.waitStatus > 0)』:說明下一個節點爲null或被取消了(waitStatus允許的狀態值中,只有’CANCELLED’是>0的)。那麼,就從隊列尾(tail)開始向前遍歷,獲取第一個非空且未被取消的節點。如果存在這樣的一個後繼節點的話(即,“s != null”),則執行『LockSupport.unpark(s.thread);』操作來喚醒這個節點的線程,此時等待隊列中第一個等待的線程就會被重新啓動,流程會回到『acquireQueued』方法,該線程會重新重試獲取該鎖,如果成功acquireQueued方法返回,否則線程會再次被掛起,等待下次喚醒後再去再去競爭獲取鎖。

Q:關於node的waitStatus爲’CANCELLED’的情況?
A:關於node的waitStatus爲’CANCELLED’的情況:比如,當這個node被中斷了,或者設置的超時時間到了,那麼說明這個線程獲取鎖失敗,那麼此時就應該將其設置爲cancelled,因爲如果該線程還需要獲取鎖的話,會重新調用獲取鎖的方法,而獲取鎖的方法就是創建一個新的node的。所以,那麼線程獲取鎖失敗的時候就會將這個node的waitStatus設置爲’CANCELLED’,一個被取消的線程絕不會獲取鎖成功,一個線程只能被它自己取消,不能被其他線程取消。

Q:關於node爲null的情況?
A:關於node爲null的情況:比如,一個入隊操作(enq)不會被分配到前驅節點的next字段,直到tail成功指向當前節點之後(通過CAS來將tail指向當前節點。『enq』方法實現中,會先將node.prev = oldTailNode;在需要在CAS成功之後,即tail = node之後,再將oldTailNode.next = node;),所以當看到next字段爲null時並不意味着當前節點是隊列的尾部了。無論如何,如果一個next字段顯示爲null,我們能夠從隊列尾向前掃描進行復核。

Q:對於ReentrantLock無論是公平鎖還是非公平鎖,在入隊時waitStatus都是什麼??
能確定的是從條件等待隊列轉移到鎖的同步隊列的時候,節點的waitStatus是’0’。
A:無論是公平鎖還是非公平鎖,在構建一個node的時候,waitStatus都是默認值’0’。然後在將node入隊到鎖的等待隊列中後就會執行『acquireQueued』來等待獲取鎖,而該方法會修改當前節點的前驅節點的waitStatus(即,『shouldParkAfterFailedAcquire(p, node)』方法)。在當前節點無法獲取鎖的時候需要被掛起前會將其前驅節點的waitStatus設置爲’Node.SIGNAL’。這樣在釋放操作中(『release』),如果釋放後發現鎖的state爲’0’,則說明鎖當前可以被其他線程獲取了,那麼就會獲取鎖的等待隊列的head節點,如果head節點的waitStatus!=0(即,head的waitStatus爲’Node.SIGNAL’或’Node.PROPAGATE’,其中’Node.PROPAGATE’是共享模式下head節點的waitStatus可能的值,在排他模式下,head節點的waitStatus是’Node.SIGNAL’或’0’),那麼說明head節點後面有等待喚醒獲取鎖的線程,那麼調用『unparkSuccessor』方法來喚醒head節點的後繼節點。

在排他鎖模式下,head節點的waitStatus不是在該節點被設置爲head節點的時候修改的。而是如果有節點入隊到等待隊列中,並且此時該節點無法獲取鎖,那麼會將其前驅節點的waitStatus設置爲’Node.SIGNAL’後,該節點對應的線程就被掛起了。所以也就是說,如果head節點後還有節點等待獲取鎖,那麼此時head節點的waitStatus自然會使’Node.SIGNAL’,這是在head節點的後繼節點入隊後等待獲取鎖的過程中設置的。而將一個節點設置爲head節點,僅是將該節點賦值給head節點,並將thread和prev屬性會被置null。

ConditionObject ———— 條件對象

條件對象用來管理那些已經獲得了一個鎖,但是卻不能做有用工作的線程。

Condition實現是AbstractQueuedSynchronizer作爲一個Lock實現的基礎。
該類的方法文檔描述了機制,而不是從鎖和條件(Condition)用戶的觀點來指定行爲規範。該類所暴露的版本通常需要伴隨着依賴於描述相關AbstractQueuedSynchronizer的條件語義的文檔。
這個類是可序列化的,但是所有字段都是transient的,所以反序列化的conditions沒有等待者。

注意,關於在ConditionObject中的描述,若無特殊說明“等待隊列”均指“條件等待隊列”,同鎖的等待隊列不同!

條件等待隊列中有效節點的waitStatus只能是“Node.CONDITION”,這說明,如果發現條件等待隊列中的節點waitStatus!=“Node.CONDITION”,則說明這個節點被取消等待條件了,那麼應該將其出條件等待隊列中移除。

// 等待隊列的頭節點
private transient Node firstWaiter;

// 等待隊列的尾節點
private transient Node lastWaiter;


  • 條件對象初始化
Condition condition = lock.newCondition()

『newCondition』

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

返回一個用於這個Lock實例的Condition實例。
返回的Condition實例與內置的監視器鎖一起使用時,支持同Object監控器方法(『Object#wait()』、『Object#notify』、『Object#notifyAll』)相同的用法。
如果這個鎖沒有被持有的話任何Condition等待(『Condition#await()』)或者通知(『Condition#signal』)方法被調用,那麼一個“IllegalMonitorStateException”異常將會拋出。(也就說是說,Condition是用在鎖已經被持有的情況下)

當一個condition的等待方法被調用(『Condition#await()』),鎖會被釋放,並且在這個方法返回之前,鎖會被重新獲取並且鎖的持有次數會重新存儲爲這個方法被調用到時候的值。
如果一個線程在等待期間被中斷了,那麼等待將會結束,一個“InterruptedException”異常將會拋出,並且線程的中斷狀態將被清除。
等待線程被以先進先出(FIFO)的順序被通知。


等待獲得鎖的線程和調用await方法的線程存在本質上的不同。一旦一個線程調用await方法,它進入該條件的等待集。當鎖可用時,該線程不能馬上解除阻塞。相反,它處於阻塞狀態,直到另一個線程調用同一條件上的signalAll方法時爲止。這一調用重新激活因爲這一條件而等待的所有線程。當這些線程從等待集當中移出時,它們再次成爲可運行的,調度器將再次激活它們。同時,它們將試圖重新進入該對象。一旦鎖成爲可用的,它們中的某個將從await調用返回,獲得該鎖並從被阻塞的地方繼續執行。

  • await
    『AbstractQueuedSynchronizer#await』
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

實現可中斷的條件等待。
1.如果當前線程被中斷了,則拋出“InterruptedException”異常。
2.保存通過『getState』返回的鎖的狀態(state)
3.調用『release』方法,使用保存的state作爲參數,如果該方法失敗則拋出“IllegalMonitorStateException”異常
4.線程阻塞直到收到喚醒通知或者線程被中斷
5.通過調用使用保存狀態爲參數的指定版本的『acquire』方法來重新獲取鎖。
6.如果在步驟4中線程在堵塞的時候被中斷了,那麼拋出“InterruptedException”異常。

① 首先一進入該方法會先判斷當前線程是否被標誌爲了中斷狀態,如果是則拋出“InterruptedException”異常,並且中斷狀態被清除。
② 調用『addConditionWaiter』添加一個新的等待節點到條件等待隊列中。
③ 調用『fullyRelease(node)』使用鎖當前的狀態值執行釋放,並返回這個狀態值。(即,該方法調用完後,並執行成功的話,那麼此時其他線程可以去獲取這個鎖了,可見await方法會使當前線程放棄對鎖的持有。同時返回鎖在釋放前的狀態值。)
④ 自旋判斷創建的等待節點是否在所的同步隊列中了,如果沒有(則說明節點還未被信號通知,以從條件等待隊列中轉移到鎖的同步隊列中),則執行『LockSupport.park(this);』掛起當前線程,線程會一直被掛起直到被信號通知喚醒(『signal()』或『signalAll()』方法會將節點從條件等待隊列轉移到鎖的同步隊列中,並根據加入到同步隊列後得到的前驅節點的waitStatus,可能會去喚醒當前線程;或者當鎖的同步等待隊列中的線程依次獲取鎖並釋放後,直到輪到當前線程成爲同步隊列中第一個等待獲取鎖的線程時,當前線程或被喚醒)。接着判斷線程是否發生中斷,如果發送中斷,則退出自旋;否則繼續自旋重新執行本步驟的流程,直至新創建的等待節點被轉移到鎖的同步隊列中。
⑤ 執行『acquireQueued(node, savedState)』方法在排他模式下從等待隊列中的線程獲取鎖,並且在獲取鎖後將鎖的狀態值設置爲給定’savedState’(由於’savedState’就是上面因條件等待而釋放鎖前該線程獲取鎖的次數,而在該線程重新獲取鎖,繼續await之後的流程時,保持了該線程在await之前持有鎖的狀態)。並且該方法會在獲取鎖的情況下才會返回:
a)若在等待獲取鎖的過程中,當前線程被標識爲了中斷,則在方法返回的時候返回true;接着判斷interruptMode是否等於“THROW_IE”,如果爲true,則說明節點的等待在得到喚醒通知之前就被取消了,此時interruptMode爲“THROW_IE”;否則interruptMode!=THROW_IE,則說明節點的等待在得到喚醒通知之後才被取消了,那麼設置interruptMode爲“REINTERRUPT”,繼續步驟[6]
b)若在等待獲取鎖的過程中,當前線程未被標識爲中斷,則繼續步驟[6]
(這裏一個正常的未被中斷的流程就是,await的節點對應的線程會在步驟[4]被掛起,然後在某一個時刻因爲signalAll()方法調用,該節點被轉移到了鎖的等待隊列中。然後當該線程爲鎖的等待隊列中第一個等待獲取鎖的線程時,會被它的前驅節點喚醒,此時節點被喚醒,判斷得到已經在等待隊列中了,那麼結束步驟[4]的自旋,進入的步驟[5],調用『acquireQueued(node, savedState)』嘗試獲取鎖,此時節點已經具有獲取鎖的權限了,如果成功獲取鎖流程繼續,否則節點會被再次掛起,acquireQueued方法會阻塞直到當前線程獲取鎖的情況下返回。)
⑥ 如果node節點的nextWaiter非null,那麼執行『unlinkCancelledWaiters();』來清除等待隊列中被取消的節點。
因爲,如果node節點是通過signal/signalAll信號通知而從條件等待隊列轉移到鎖的同步隊列的話,那麼node的nextWaiter是爲null(在signal/signalAll方法中會將該字段置爲null);否則如果是因爲中斷而將節點從條件等待隊列轉移到鎖的同步隊列的話,此時nextWaiter是不會被重置的,它依舊指向該節點在條件等待隊列中的下一個節點。
⑦ 如果中斷模式標誌不爲’0’(即,“interruptMode != 0”),則根據給定的中斷模式(interruptMode)在等待結束後報告中斷(『reportInterruptAfterWait(interruptMode)』)

因此,從『await()』方法中,我們可以得知下面幾點:

  1. 創建一個條件等待節點,並加入條件等待隊列尾部。
  2. 徹底釋放當前線程所持有鎖(因爲,首先只有在持有鎖的情況下才可以執行await操作,再者ReentrantLock是一個可重入的鎖,因此同一個線程可以多次獲取鎖),這樣鎖就可以被其他線程獲取。但會記錄這個線程在徹底釋放鎖之前持有該鎖的次數(即,鎖的state值)
  3. 在該線程再次獲取該鎖時,會將鎖的state設置爲釋放之前的值。即,從await()條件等待返回的時候,當前線程對鎖持有的狀態同await()等待條件之前是一致的。
  4. 節點從條件等待隊列中轉移到鎖的同步隊列是兩種情況:
    a)收到了信號通知,即signal/signalAll
    b)在未收到信號通知之前,檢測到了當前線程被中斷的標誌。
  5. 在當前線程重新獲取到鎖,準備從await方法返回的時候,await方法的返回也分兩種情況:
    a)在條件等待中的節點是通過signal/signalAll信號通知轉移到鎖的同步隊列的,然後再在同步隊列中根據FIFO的順序來重新獲取到了該鎖。那麼此時await方法正常返回。(在信號通知之後線程可能被標誌位中斷,但這不影響方法的正常返回)
    b)在條件等待中節點是因爲當前線程被標誌爲了中斷而將其轉移到了鎖的同步隊列中,這樣在當前線程再次重新獲取鎖時,方法會異常返回,即拋出“InterruptedException”異常。


    接下來對,『await』中的源碼細節進一步展開
    『AbstractQueuedSynchronizer#addConditionWaiter』
private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

添加一個新的等待者到等待隊列中。
①『Node t = lastWaiter;』:獲取等待隊列的中的最後一個節點,
②『t != null && t.waitStatus != Node.CONDITION』如果這個節點被取消了,那麼調用『unlinkCancelledWaiters();』方法將等待隊列中被取消的節點移除。並重新獲取等待隊列中的最後一個節點(『t = lastWaiter』)
③ 爲當前線程創建一個waitStatus爲“Node.CONDITION”的節點。
④ 將新創建好的節點加入到等待隊列的尾部:
a)如當前等待隊列爲空(即,上面獲取的t爲null,也就是說,當等待隊列尾指針爲null時,則說明此時等待隊列爲空)那麼需要先初始化firstWaiter,將其指向這個新創建的節點。然後將lastWaiter也指向這個新創建的節點。此時等待隊列中只有一個節點,firstWaiter和lastWaiter都指向這個節點。
b)將等待隊列中最後一個節點的next屬性指向當前這個新創建的節點,然後將lastWaiter指向當前這個新創建的節點。
⑤ 返回新創建的等待節點。


『AbstractQueuedSynchronizer#fullyRelease』

final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        int savedState = getState();
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

使用當前的狀態值執行釋放;返回保存的狀態值。
若操作失敗,則取消節點,並拋出異常。
①『int savedState = getState();』獲取當前鎖的狀態值
② 使用獲取的狀態值執行釋放操作『release(savedState)』,如果操作成功,則方法結束,返回釋放使用的保存的狀態值;如果操作失敗,則拋出“IllegalMonitorStateException”異常,並取消node節點,即,將節點node的waitStatus設置爲“Node.CANCELLED”。


『AbstractQueuedSynchronizer#isOnSyncQueue』

final boolean isOnSyncQueue(Node node) {
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    if (node.next != null) // If has successor, it must be on queue
        return true;
    /*
     * node.prev can be non-null, but not yet on queue because
     * the CAS to place it on queue can fail. So we have to
     * traverse from tail to make sure it actually made it.  It
     * will always be near the tail in calls to this method, and
     * unless the CAS failed (which is unlikely), it will be
     * there, so we hardly ever traverse much.
     */
    return findNodeFromTail(node);
}

返回true,如果一個節點總是初始化於條件隊列中,並且當前在同步隊列中等待獲取鎖。
① 如果node的waitStatus爲“Node.CONDITION”或者node的prev爲null,則說明node節點當前還沒有入隊同步隊列,方法結束,返回false;否則步驟[2]
② 接着判斷『if (node.next != null)』,如果爲true,則說明node已經入隊完畢,則方法結束,返回true。否則步驟[3]
③ 調用『findNodeFromTail(node)』從同步隊列尾開始尋找節點。此時,node.prev非null,但是由於通過CAS將節點入隊的操作可能失敗導致當前節點還未在同步隊列中(即,節點入隊操作還未完成)。所以我們需要從同步隊列尾部開始向前遍歷以明確該節點是否在同步隊列中。在這種方法的調用中,節點總是靠近尾部,除非CAS失敗(不太可能),否則節點將在同步隊列尾部附近,所以我們幾乎不會經歷很多遍歷。


『AbstractQueuedSynchronizer#findNodeFromTail』

private boolean findNodeFromTail(Node node) {
    Node t = tail;
    for (;;) {
        if (t == node)
            return true;
        if (t == null)
            return false;
        t = t.prev;
    }
}

從同步隊列尾向前查詢節點,如果節點在同步隊列中,則返回true。
僅在『isOnSyncQueue』方法內調用該方法。
從鎖的等待隊列尾部開始向前遍歷,如果找到node節點則返回true;否則遍歷完整個等待隊列也就沒法找到node節點,則返回false。


『AbstractQueuedSynchronizer#checkInterruptWhileWaiting』

private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}

用於檢測中斷,如果中斷在信號通知之前發生則返回“THROW_IE”,若中斷在信號通知之後發生則返回“REINTERRUPT”,或者如果沒有被中斷則返回“0”。


『AbstractQueuedSynchronizer#transferAfterCancelledWait』

final boolean transferAfterCancelledWait(Node node) {
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        enq(node);
        return true;
    }
    /*
     * If we lost out to a signal(), then we can't proceed
     * until it finishes its enq().  Cancelling during an
     * incomplete transfer is both rare and transient, so just
     * spin.
     */
    while (!isOnSyncQueue(node))
        Thread.yield();
    return false;
}

如果需要的話,在取消等待後,將節點轉移到同步隊列。如果線程在被信號通知之前取消等待了則返回true。
① 通過CAS的方式將節點的狀態從“Node.CONDITION”修改爲“0”,如果成功,則說明節點此時還沒有收到信號通知,此時將節點的waitStatus從“Node.CONDITION”修改爲“0”就是在被信號通知前取消了節點對條件的等待,接着調用『enq(node)』將節點入隊到鎖的等待隊列中,並結束方法,返回true。
② CAS操作失敗,則說明該等待條件的節點被其他線程信號通知了(一般是signalAll),那麼自旋調用『isOnSyncQueue(node)』以確保節點入隊(鎖的等待隊列)完成後退出自旋(因爲取消等待條件期間一個未完成的轉換是罕見且瞬間的時期,所以使用自旋即可)。然後方法結束,返回false。
也就是說,首先該方法會確保node從條件等待隊列轉移到鎖的同步隊列中。node是因爲該方法的執行而從條件等待隊列轉移到鎖的同步隊列的話,則返回true;否則如果node是因爲signal/signalAll信號通知而從條件等待隊列轉移到鎖的同步隊列的話,則返回false。


『AbstractQueuedSynchronizer#enq』

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;
            }
        }
    }
}

使用自旋鎖的方式(自旋+CAS)插入節點到等待隊列,如果等待隊列爲空則初始化隊列。
初始化隊列:創建一個空節點(即,new Node()),將head和tail都指向這個節點。
然後纔是將我們待插入的節點插入,即:emptyNode -> newNode. head指向emptyNode,tail指向newNode。


『AbstractQueuedSynchronizer#unlinkCancelledWaiters』

private void unlinkCancelledWaiters() {
    Node t = firstWaiter;
    Node trail = null;
    while (t != null) {
        Node next = t.nextWaiter;
        if (t.waitStatus != Node.CONDITION) {
            t.nextWaiter = null;
            if (trail == null)
                firstWaiter = next;
            else
                trail.nextWaiter = next;
            if (next == null)
                lastWaiter = trail;
        }
        else
            trail = t;
        t = next;
    }
}

從等待隊列中解除被取消等待節點的連接。該方法僅在持有鎖的時候調用。這個方法調用發生在取消發生在等待條件期間,並根據一個新的等待節點插入時lastWaiter看起來已經被取消了。這個方法需要去避免垃圾的滯留在沒有信號通知的時候。所以即便它可能需要一個完全遍歷,這僅會在超時和取消發生在缺少通知的情況下發生。它會遍歷所有的節點而非停止在一個指定的目標,以便在取消風暴期間不需要多次重新遍歷就可以將所有的垃圾節點解除鏈接。
該方法會從firstWaiter開始遍歷整個等待隊列,將被取消(即,waitStatus != Node.CONDITION)的節點從等待隊列中移除。
①『Node t = firstWaiter;』:獲取等待隊列的頭結點。
② 從頭節點開始遍歷等待隊列。
③『Node next = t.nextWaiter;』獲取當前遍歷節點的下一個節點。
④ 如果當前節點被取消了(即,『t.waitStatus != Node.CONDITION』),那麼將當前節點的next字段置null(便於垃圾回收)。然後判斷『trail == null』,如果爲true,則說明目前是頭節點被取消了,那麼設置『firstWaiter=next』,即當前節點的下一個節點。此時,next節點可能是一個有效節點,也可能是一個被取消的節點(如果是被取消的節點,會在下一次循環的時候再次重新設置firstWaiter),也可能是一個null(如果爲null,接下來就會退出循環,說明等待隊列爲空了);如果『trail == null』爲false,則說明此遍歷到的被取消的節點不是頭節點,並且trail指向了遍歷到目前爲止等待隊列中最後一個有效的等待節點,那麼執行『trail.nextWaiter = next;』以將當前正在被遍歷的節點從等待隊列中解除連接。接着判斷『next == null』,若爲true,則說明當前遍歷的被取消的節點是等待隊列的最後一個節點,那麼執行『lastWaiter = trail;』將lastWaiter指向最後一個有效的等待節點。
⑤ 如果當前節點沒有被取消(即,『t.waitStatus == Node.CONDITION』),那麼將trail置爲t,這說明了trail指向了在遍歷等待隊列過程中的最後一個有效的等待節點。
⑥ 將t置爲next,即當前遍歷節點的下一個節點。繼續步驟[3],直至整個等待隊列節點都遍歷完(即,next爲null)。

signal/signalAll
  • ** signal**
public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}
 

將當前的條件等待隊列中將等待時間最長的線程移動到鎖的等待隊列中,如果存在這麼一個線程的話。
① 判斷執行該方法的當前線程是否是持有排他鎖的線程,如果不是則拋出“IllegalMonitorStateException”異常。
② 當執行該方法的線程是持有排他鎖的線程時,獲取條件等待隊列中的第一個等待節點,若這個節點不爲null,則執行『doSignal(first)』來信號通知這個節點。
注意,因爲條件等待節點是按照FIFO的順序操作節點的,也就是新的等待節點總是會添加對隊列尾部,所以隊列頭節點就是等待最長時間的節點。


『AbstractQueuedSynchronizer#doSignal』

private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

刪除並轉移節點,直到命中一個未被取消的節點或者節點爲null(節點爲null,說明等待隊列中已經沒有一個有效的節點了,即等待隊列要麼爲空,要麼等待隊列中的節點都是被取消的節點)。
① 根據給定的first節點,爲起始遍歷直至獲取第一個有效等待節點,並信號通知該節點。
② 將當前節點的下一個等待節點(nextWaiter)設置爲firstWaiter,然後判斷firstWaiter是否爲null,如果爲null則說明當前節點已經是條件等待隊列中的最後一個節點了,那麼將lastWaiter也置爲null。
③ 將當前遍歷節點的nextWaiter置爲null(以便於當前節點在方法結束後被垃圾收集器回收)
④ 執行『transferForSignal』將節點從條件等待隊列轉移到同步隊列隊列中,如果操作成功,則當前循環結束,方法返回;如果操作失敗,那麼繼續從頭節點開始循環步驟[2],直到成功轉移一個節點或者條件等待隊列爲空爲止。

  • signalAll
public final void signalAll() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignalAll(first);
}

將條件等待隊列中的所有線程轉移到鎖的等待隊列中。
① 判斷執行該方法的當前線程是否是持有排他鎖的線程,如果不是則拋出“IllegalMonitorStateException”異常。
② 當執行該方法的線程是持有排他鎖的線程時,獲取條件等待隊列中的第一個等待節點,若這個節點不爲null,則執行『doSignalAll(first)』來信號通知所有的節點。


『AbstractQueuedSynchronizer#doSignalAll』

private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        transferForSignal(first);
        first = next;
    } while (first != null);
}

刪除並轉移所有的節點。
① 將lastWaiter和firstWaiter置爲null
② 從給定的節點爲起始,開始遍歷節點,調用『transferForSignal(first);』來將節點從條件等待隊列中轉移到鎖的等待隊列中。


『isHeldExclusively』

protected final boolean isHeldExclusively() {
    // While we must in general read state before owner,
    // we don't need to do so to check if current thread is owner
    return getExclusiveOwnerThread() == Thread.currentThread();
}
/code>

判斷執行該方法的當前線程是否是持有排他鎖的線程。
如下情況,返回’true’:
a)執行該方法的線程就是持有排他鎖的線程。
如下情況,返回’false’:
a)執行該方法的線程不是持有排他鎖的線程。
b)當前排他鎖沒有被任何線程所持有。


『AbstractQueuedSynchronizer#transferForSignal』

final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
<span class="token comment">/*
 * Splice onto queue and try to set waitStatus of predecessor to
 * indicate that thread is (probably) waiting. If cancelled or
 * attempt to set waitStatus fails, wake up to resync (in which
 * case the waitStatus can be transiently and harmlessly wrong).
 */</span>
<span class="token class-name">Node</span> p <span class="token operator">=</span> <span class="token function">enq</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">int</span> ws <span class="token operator">=</span> p<span class="token punctuation">.</span>waitStatus<span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>ws <span class="token operator">&gt;</span> <span class="token number">0</span> <span class="token operator">||</span> <span class="token operator">!</span><span class="token function">compareAndSetWaitStatus</span><span class="token punctuation">(</span>p<span class="token punctuation">,</span> ws<span class="token punctuation">,</span> <span class="token class-name">Node</span><span class="token punctuation">.</span>SIGNAL<span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token class-name">LockSupport</span><span class="token punctuation">.</span><span class="token function">unpark</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>thread<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>

}

從條件等待隊列轉移一個節點到同步隊列中。如果成功返回true
返回:
a)true:成功從條件隊列中轉移一個節點到同步隊列中
b)false:在釋放信號通知之前,該節點被取消了。
① 通過CAS的方式將需要轉移的節點的狀態從“Node.CONDITION”修改爲“0”。如果CAS操作失敗,則說明這個節點的已經被取消了。那麼方法結束,返回false。
② 將修改完狀態後的節點加入到鎖等待隊列中(『enq(node)』),並得到加入到等待隊列後,當前節點的前驅節點。
③ 若前驅節點的"waitStatus > 0”(即,waitStatus爲“CANCELLED”)或者通過CAS的方式將前驅節點的waitStatus修改爲“SIGNAL”失敗,則調用『LockSupport.unpark(node.thread);』將當前線程喚醒(喚醒後的線程會繼續await中被掛起之後的流程)。
④ 否則,"waitStatus <= 0”並且通過CAS成功將前驅節點的waitStatus修改爲了“SIGNAL”,以此來標識當前線程正在等待獲取鎖。

  • 『signal』vs『signalAll』
    signal解除的是條件等待隊列中第一個有效的節點(即,節點的waitStatus爲“CONDITION”),這比解除所有線程的阻塞更加有效,但也存在危險。如果signal的線程發現自己仍然不能運行,那麼它再次被阻塞(await)。如果沒有其他線程再次調用signal,那麼系統就死鎖了。

signal/signalAll方法本質上只是將條件等待隊列中的節點轉移到鎖的同步隊列中。因此,不能任務signal/signalAll方法調用後就會使得線程獲取鎖,線程什麼時候獲取鎖,就是根據鎖的同步隊列FIFO的順序來決定的,只有同步隊列中的第一個線程纔有權利去爭奪獲取鎖。

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