JDK源碼解析實(shí)戰(zhàn) - AbstractQueuedSynchronizer源碼解析

AbstractQueuedSynchronizer 抽象同步隊(duì)列簡(jiǎn)稱 AQS 闷煤,它是實(shí)現(xiàn)同步器的基礎(chǔ)組件撞秋,
并發(fā)包中鎖的底層就是使用 AQS 實(shí)現(xiàn)的.
大多數(shù)開發(fā)者可能永遠(yuǎn)不會(huì)直接使用AQS ,但是知道其原理對(duì)于架構(gòu)設(shè)計(jì)還是很有幫助的,而且要理解ReentrantLock、CountDownLatch等高級(jí)鎖我們必須搞懂 AQS.

1 整體感知

1.1 架構(gòu)圖

image

AQS框架大致分為五層恃慧,自上而下由淺入深,從AQS對(duì)外暴露的API到底層基礎(chǔ)數(shù)據(jù).

當(dāng)有自定義同步器接入時(shí)渺蒿,只需重寫第一層所需要的部分方法即可痢士,不需要關(guān)注底層具體的實(shí)現(xiàn)流程。當(dāng)自定義同步器進(jìn)行加鎖或者解鎖操作時(shí)茂装,先經(jīng)過(guò)第一層的API進(jìn)入AQS內(nèi)部方法怠蹂,然后經(jīng)過(guò)第二層進(jìn)行鎖的獲取,接著對(duì)于獲取鎖失敗的流程少态,進(jìn)入第三層和第四層的等待隊(duì)列處理城侧,而這些處理方式均依賴于第五層的基礎(chǔ)數(shù)據(jù)提供層。

AQS 本身就是一套鎖的框架彼妻,它定義了獲得鎖和釋放鎖的代碼結(jié)構(gòu)嫌佑,所以如果要新建鎖,只要繼承 AQS侨歉,并實(shí)現(xiàn)相應(yīng)方法即可屋摇。

1.2 類設(shè)計(jì)

該類提供了一種框架,用于實(shí)現(xiàn)依賴于先進(jìn)先出(FIFO)等待隊(duì)列的阻塞鎖和相關(guān)的同步器(信號(hào)量幽邓,事件等)炮温。此類的設(shè)計(jì)旨在為大多數(shù)依賴單個(gè)原子int值表示 state 的同步器提供切實(shí)有用的基礎(chǔ)。子類必須定義更改此 state 的 protected 方法牵舵,并定義該 state 對(duì)于 acquired 或 released 此對(duì)象而言意味著什么柒啤。鑒于這些倦挂,此類中的其他方法將執(zhí)行全局的排隊(duì)和阻塞機(jī)制。子類可以維護(hù)其他狀態(tài)字段担巩,但是就同步而言方援,僅跟蹤使用方法 getStatesetStatecompareAndSetState 操作的原子更新的int值涛癌。
子類應(yīng)定義為用于實(shí)現(xiàn)其所在類的同步屬性的非公共內(nèi)部幫助器類肯骇。

子類應(yīng)定義為用于實(shí)現(xiàn)其所在類的同步屬性的非 public 內(nèi)部輔助類。類AbstractQueuedSynchronizer不實(shí)現(xiàn)任何同步接口祖很。 相反笛丙,它定義了諸如acquireInterruptible之類的方法,可以通過(guò)具體的鎖和相關(guān)的同步器適當(dāng)?shù)卣{(diào)用這些方法來(lái)實(shí)現(xiàn)其 public 方法假颇。

此類支持默認(rèn)的排他模式和共享模式:

  • 當(dāng)以獨(dú)占方式進(jìn)行獲取時(shí)胚鸯,其他線程嘗試進(jìn)行的獲取將無(wú)法成功
  • 由多個(gè)線程獲取的共享模式可能(但不一定)成功

該類不理解這些差異,只是從機(jī)制的意義上說(shuō)笨鸡,當(dāng)共享模式獲取成功時(shí)姜钳,下一個(gè)等待線程(如果存在)也必須確定它是否也可以獲取。在不同模式下等待的線程們共享相同的FIFO隊(duì)列形耗。 通常哥桥,實(shí)現(xiàn)的子類僅支持這些模式之一,但也可以同時(shí)出現(xiàn),比如在ReadWriteLock.僅支持排他模式或共享模式的子類無(wú)需定義支持未使用模式的方法.

此類定義了一個(gè)內(nèi)嵌的 ConditionObject 類激涤,可以由支持排他模式的子類用作Condition 的實(shí)現(xiàn)拟糕,該子類的 isHeldExclusively 方法報(bào)告相對(duì)于當(dāng)前線程是否獨(dú)占同步,使用當(dāng)前 getState 值調(diào)用的方法 release 會(huì)完全釋放此對(duì)象 倦踢,并獲得給定的此保存狀態(tài)值送滞,最終將該對(duì)象恢復(fù)為其先前的獲取狀態(tài)。否則辱挥,沒有AbstractQueuedSynchronizer方***創(chuàng)建這樣的條件犁嗅,因此,如果無(wú)法滿足此約束晤碘,請(qǐng)不要使用它褂微。ConditionObject的行為當(dāng)然取決于其同步器實(shí)現(xiàn)的語(yǔ)義。

此類提供了內(nèi)部隊(duì)列的檢查园爷,檢測(cè)和監(jiān)視方法宠蚂,以及條件對(duì)象的類似方法。 可以根據(jù)需要使用 AQS 將它們導(dǎo)出到類中以實(shí)現(xiàn)其同步機(jī)制腮介。

此類的序列化僅存儲(chǔ)基礎(chǔ)原子整數(shù)維護(hù)狀態(tài)肥矢,因此反序列化的對(duì)象具有空線程隊(duì)列。 需要序列化性的典型子類將定義一個(gè)readObject方法叠洗,該方法在反序列化時(shí)將其恢復(fù)為已知的初始狀態(tài)甘改。

2 用法

要將此類用作同步器的基礎(chǔ),使用getState setState和/或compareAndSetState檢查和/或修改同步狀態(tài)灭抑,以重新定義以下方法(如適用)

  • tryAcquire
  • tryRelease
  • tryAcquireShared
  • tryReleaseShared
  • isHeldExclusively

默認(rèn)情況下十艾,這些方法中的每一個(gè)都會(huì)拋 UnsupportedOperationException
這些方法的實(shí)現(xiàn)必須在內(nèi)部是線程安全的腾节,并且通常應(yīng)簡(jiǎn)短且不阻塞忘嫉。 定義這些方法是使用此類的唯一受支持的方法。 所有其他方法都被聲明為final案腺,因?yàn)樗鼈儾荒塥?dú)立變化庆冕。

從 AQS 繼承的方法對(duì)跟蹤擁有排他同步器的線程很有用。 鼓勵(lì)使用它們-這將啟用監(jiān)視和診斷工具劈榨,以幫助用戶確定哪些線程持有鎖访递。

雖然此類基于內(nèi)部的FIFO隊(duì)列,它也不會(huì)自動(dòng)執(zhí)行FIFO獲取策略同辣。 獨(dú)占同步的核心采用以下形式:

  • Acquire
while (!tryAcquire(arg)) {
     如果線程尚未入隊(duì)拷姿,則將其加入隊(duì)列;
     可能阻塞當(dāng)前線程旱函;
}
  • Release
if (tryRelease(arg))
    取消阻塞第一個(gè)入隊(duì)的線程;

共享模式與此相似响巢,但可能涉及級(jí)聯(lián)的signal.

因?yàn)?acquire 中的檢查是入隊(duì)前被調(diào)用的,所以新獲取的線程可能會(huì)在被阻塞和排隊(duì)的其他線程之前插入棒妨。 但是踪古,如果需要,可以定義tryAcquire和/或tryAcquireShared以通過(guò)內(nèi)部調(diào)用一種或多種檢查方法來(lái)禁用插入券腔,從而提供公平的FIFO獲取順序灾炭。 特別是,如果hasQueuedPredecessors()(公平同步器專門設(shè)計(jì)的一種方法)返回true颅眶,則大多數(shù)公平同步器都可以定義tryAcquire返回false.

對(duì)于默認(rèn)的插入(也稱為貪婪蜈出,放棄和convoey -avoidance)策略,吞吐量和可伸縮性通常最高涛酗。 盡管不能保證這是公平的或避免饑餓铡原,但允許較早排隊(duì)的線程在較晚排隊(duì)的線程之前進(jìn)行重新競(jìng)爭(zhēng),并且每個(gè)重新爭(zhēng)用都有一次機(jī)會(huì)可以毫無(wú)偏向地成功競(jìng)爭(zhēng)過(guò)進(jìn)入的線程。 同樣商叹,盡管獲取通常不需要自旋燕刻,但在阻塞之前,它們可能會(huì)執(zhí)行tryAcquire的多次調(diào)用剖笙,并插入其他任務(wù)卵洗。 如果僅短暫地保持排他同步,則這將帶來(lái)自旋的大部分好處,而如果不進(jìn)行排他同步过蹂,則不會(huì)帶來(lái)很多負(fù)擔(dān)十绑。 如果需要的話,可以通過(guò)在調(diào)用之前使用“fast-path”檢查來(lái)獲取方法來(lái)增強(qiáng)此功能酷勺,并可能預(yù)先檢查hasContended()和/或hasQueuedThreads(),以便僅在同步器可能不存在爭(zhēng)用的情況下這樣做本橙。

此類為同步提供了有效且可擴(kuò)展的基礎(chǔ),部分是通過(guò)將其使用范圍規(guī)范化到可以依賴于int狀態(tài)脆诉,acquire 和 release 參數(shù)以及內(nèi)部的FIFO等待隊(duì)列的同步器甚亭。 當(dāng)這還不夠時(shí),可以使用原子類击胜、自定義隊(duì)列類和鎖支持阻塞支持從較低級(jí)別構(gòu)建同步器亏狰。

3 使用案例

這里是一個(gè)不可重入的排他鎖,它使用值0表示解鎖狀態(tài)偶摔,使用值1表示鎖定狀態(tài)暇唾。雖然不可重入鎖并不嚴(yán)格要求記錄當(dāng)前所有者線程,但是這個(gè)類這樣做是為了更容易監(jiān)視使用情況啰挪。它還支持條件信不,并暴露其中一個(gè)檢測(cè)方法:

 class Mutex implements Lock, java.io.Serializable {

   // 我們內(nèi)部的輔助類
   private static class Sync extends AbstractQueuedSynchronizer {
     // 報(bào)告是否處于鎖定狀態(tài)
     protected boolean isHeldExclusively() {
       return getState() == 1;
     }

     // 如果 state 是 0,獲取鎖
     public boolean tryAcquire(int acquires) {
       assert acquires == 1; // Otherwise unused
       if (compareAndSetState(0, 1)) {
         setExclusiveOwnerThread(Thread.currentThread());
         return true;
       }
       return false;
     }

     // 通過(guò)將 state 置 0 來(lái)釋放鎖
     protected boolean tryRelease(int releases) {
       assert releases == 1; // Otherwise unused
       if (getState() == 0) throw new IllegalMonitorStateException();
       setExclusiveOwnerThread(null);
       setState(0);
       return true;
     }

     //  提供一個(gè) Condition
     Condition newCondition() { return new ConditionObject(); }

     // 反序列化屬性
     private void readObject(ObjectInputStream s)
         throws IOException, ClassNotFoundException {
       s.defaultReadObject();
       setState(0); // 重置到解鎖狀態(tài)
     }
   }

   // 同步對(duì)象完成所有的工作。我們只是期待它.
   private final Sync sync = new Sync();

   public void lock()                { sync.acquire(1); }
   public boolean tryLock()          { return sync.tryAcquire(1); }
   public void unlock()              { sync.release(1); }
   public Condition newCondition()   { return sync.newCondition(); }
   public boolean isLocked()         { return sync.isHeldExclusively(); }
   public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
   public void lockInterruptibly() throws InterruptedException {
     sync.acquireInterruptibly(1);
   }
   public boolean tryLock(long timeout, TimeUnit unit)
       throws InterruptedException {
     return sync.tryAcquireNanos(1, unit.toNanos(timeout));
   }
 }

這是一個(gè)閂鎖類亡呵,它類似于CountDownLatch抽活,只是它只需要一個(gè)單信號(hào)就可以觸發(fā)。因?yàn)殒i存器是非獨(dú)占的锰什,所以它使用共享的獲取和釋放方法下硕。

 class BooleanLatch {

   private static class Sync extends AbstractQueuedSynchronizer {
     boolean isSignalled() { return getState() != 0; }

     protected int tryAcquireShared(int ignore) {
       return isSignalled() ? 1 : -1;
     }

     protected boolean tryReleaseShared(int ignore) {
       setState(1);
       return true;
     }
   }

   private final Sync sync = new Sync();
   public boolean isSignalled() { return sync.isSignalled(); }
   public void signal()         { sync.releaseShared(1); }
   public void await() throws InterruptedException {
     sync.acquireSharedInterruptibly(1);
   }
 }

4 基本屬性與框架

4.1 繼承體系圖

image

4.2 定義

image

可知 AQS 是一個(gè)抽象類,生來(lái)就是被各種子類鎖繼承的汁胆。繼承自AbstractOwnableSynchronizer梭姓,其作用就是為了知道當(dāng)前是哪個(gè)線程獲得了鎖,便于后續(xù)的監(jiān)控


image

4.3 屬性

4.3.1 狀態(tài)信息

  • volatile 修飾嫩码,對(duì)于可重入鎖誉尖,每次獲得鎖 +1,釋放鎖 -1


    image
  • 可以通過(guò) getState 得到同步狀態(tài)的當(dāng)前值铸题。該操作具有 volatile 讀的內(nèi)存語(yǔ)義铡恕。

    image

  • setState 設(shè)置同步狀態(tài)的值。該操作具有 volatile 寫的內(nèi)存語(yǔ)義


    image
  • compareAndSetState 如果當(dāng)前狀態(tài)值等于期望值丢间,則以原子方式將同步狀態(tài)設(shè)置為給定的更新值探熔。此操作具有 volatile 讀和寫的內(nèi)存語(yǔ)義


    image
  • 自旋比使用定時(shí)掛起更快乓梨。粗略估計(jì)足以在非常短的超時(shí)時(shí)間內(nèi)提高響應(yīng)能力如叼,當(dāng)設(shè)置等待時(shí)間時(shí)才會(huì)用到這個(gè)屬性


    image

這寫方法都是Final的,子類無(wú)法重寫黄绩。

  • 獨(dú)占模式


    image
  • 共享模式


    image

4.3.2 同步隊(duì)列

  • 作用
    阻塞獲取不到鎖(獨(dú)占鎖)的線程,并在適當(dāng)時(shí)機(jī)從隊(duì)首釋放這些線程

同步隊(duì)列底層數(shù)據(jù)結(jié)構(gòu)是個(gè)雙向鏈表

  • 等待隊(duì)列的頭其垄,延遲初始化苛蒲。 除初始化外,只能通過(guò) setHead 方法修改

    image

    注意:如果head存在捉捅,則其waitStatus保證不會(huì)是 CANCELLED

  • 等待隊(duì)列的尾部撤防,延遲初始化虽风。 僅通過(guò)方法 enq 修改以添加新的等待節(jié)點(diǎn)

    image

4.3.4 條件隊(duì)列

為什么需要條件隊(duì)列

個(gè)同步隊(duì)列并不是所有場(chǎng)景都能搞定棒口,在遇到鎖 + 隊(duì)列結(jié)合的場(chǎng)景時(shí),就需要 Lock + Condition辜膝,先使用 Lock 決定

  • 哪些線程可以獲得鎖
  • 哪些線程需要到同步隊(duì)列里面排隊(duì)阻塞

獲得鎖的多個(gè)線程在碰到隊(duì)列滿或者空的時(shí)候无牵,可以使用 Condition 來(lái)管理這些線程,讓這些線程阻塞等待厂抖,然后在合適的時(shí)機(jī)后茎毁,被正常喚醒。

同步隊(duì)列 + 條件隊(duì)列聯(lián)手使用的場(chǎng)景忱辅,最多被使用到鎖 + 隊(duì)列的場(chǎng)景中七蜘。

作用

AQS 的內(nèi)部類,結(jié)合鎖實(shí)現(xiàn)線程同步墙懂。存放調(diào)用條件變量的 await 方法后被阻塞的線程

  • 實(shí)現(xiàn)了 Condition 接口橡卤,而 Condition 接口就相當(dāng)于 Object 的各種監(jiān)控方法


    image

    需要使用時(shí),直接 new ConditionObject()损搬。

4.3.5 Node

同步隊(duì)列和條件隊(duì)列的共用節(jié)點(diǎn)碧库。
入隊(duì)時(shí),用 Node 把線程包裝一下巧勤,然后把 Node 放入兩個(gè)隊(duì)列中嵌灰,我們看下 Node 的數(shù)據(jù)結(jié)構(gòu),如下:

4.3.5.1 模式

  • 共享模式


    image
  • 獨(dú)占模式


    image

4.3.5.2 等待狀態(tài)

image

注意等待狀態(tài)僅能為如下值

SIGNAL
  • 同步隊(duì)列中的節(jié)點(diǎn)在自旋獲取鎖時(shí)颅悉,如果前一個(gè)節(jié)點(diǎn)的狀態(tài)是 SIGNAL沽瞭,那么自己就直接被阻塞,否則會(huì)一直自旋
  • 該節(jié)點(diǎn)的后繼節(jié)點(diǎn)會(huì)被(或很快)阻塞(通過(guò)park)剩瓶,因此當(dāng)前節(jié)點(diǎn)釋放或取消時(shí)必須unpark其后繼節(jié)點(diǎn)驹溃。為了避免競(jìng)爭(zhēng),acquire方法必須首先指示它們需要一個(gè) signal儒搭,然后重試原子獲取吠架,然后在失敗時(shí)阻塞。


    image
CANCELLED
  • 指示線程已被取消
    image

    取消:由于超時(shí)或中斷搂鲫,該節(jié)點(diǎn)被取消傍药。
    節(jié)點(diǎn)永遠(yuǎn)不會(huì)離開此狀態(tài),即此為一種終極狀態(tài)。特別是拐辽,具有 cancelled 節(jié)點(diǎn)的線程永遠(yuǎn)不會(huì)再次阻塞拣挪。

CONDITION

該節(jié)點(diǎn)當(dāng)前在條件隊(duì)列中,當(dāng)節(jié)點(diǎn)從同步隊(duì)列被轉(zhuǎn)移到條件隊(duì)列時(shí)俱诸,狀態(tài)就會(huì)被更改成 CONDITION

image

在被轉(zhuǎn)移之前菠劝,它不會(huì)用作同步隊(duì)列的節(jié)點(diǎn),此時(shí)狀態(tài)將設(shè)置為0(該值的使用與該字段的其他用途無(wú)關(guān)睁搭,僅僅是簡(jiǎn)化了機(jī)制)赶诊。

PROPAGATE

線程處在 SHARED 情景下,該字段才會(huì)啟用园骆。

  • 指示下一個(gè)acquireShared應(yīng)該無(wú)條件傳播舔痪,共享模式下,該狀態(tài)的進(jìn)程處于 Runnable 狀態(tài)
    image

    releaseShared 應(yīng)該傳播到其他節(jié)點(diǎn)锌唾。 在doReleaseShared中對(duì)此進(jìn)行了設(shè)置(僅適用于頭節(jié)點(diǎn))锄码,以確保傳播繼續(xù)進(jìn)行,即使此后進(jìn)行了其他操作也是如此晌涕。
0

以上都不是滋捶,初始化狀態(tài)。

小結(jié)

這些值是以數(shù)字方式排列余黎,極大方便了開發(fā)者的使用重窟。我們?cè)谄綍r(shí)開發(fā)也可以定義一些有特殊意義的常量值。

非負(fù)值表示節(jié)點(diǎn)不需要 signal驯耻。 因此亲族,大多數(shù)代碼并不需要檢查特定值,檢查符號(hào)即可可缚。

  • 對(duì)于普通的同步節(jié)點(diǎn)霎迫,該字段初始化為0
  • 對(duì)于條件節(jié)點(diǎn),該字段初始化為CONDITION

使用CAS(或在可能的情況下進(jìn)行無(wú)條件的 volatile 寫)對(duì)其進(jìn)行修改帘靡。

注意兩個(gè)狀態(tài)的區(qū)別

  • state 是鎖的狀態(tài)知给,int 型,子類繼承 AQS 時(shí)描姚,都是要根據(jù) state 字段來(lái)判斷有無(wú)得到鎖
  • waitStatus 是節(jié)點(diǎn)(Node)的狀態(tài)

4.3.5.3 數(shù)據(jù)結(jié)構(gòu)

前驅(qū)節(jié)點(diǎn)
  • 鏈接到當(dāng)前節(jié)點(diǎn)/線程所依賴的用來(lái)檢查 waitStatus 的前驅(qū)節(jié)點(diǎn)
    image

在入隊(duì)期間賦值涩赢,并且僅在出隊(duì)時(shí)將其清空(為了GC)。

此外轩勘,在取消一個(gè)前驅(qū)結(jié)點(diǎn)后筒扒,在找到一個(gè)未取消的節(jié)點(diǎn)后會(huì)短路,這將始終存在绊寻,因?yàn)轭^節(jié)點(diǎn)永遠(yuǎn)不會(huì)被取消:只有成功 acquire 后花墩,一個(gè)節(jié)點(diǎn)才會(huì)變?yōu)轭^悬秉。

取消的線程永遠(yuǎn)不會(huì)成功獲取,并且線程只會(huì)取消自身冰蘑,不會(huì)取消任何其他節(jié)點(diǎn)和泌。

后繼節(jié)點(diǎn)

鏈接到后繼節(jié)點(diǎn),當(dāng)前節(jié)點(diǎn)/線程在釋放時(shí)將其unpark祠肥。 在入隊(duì)時(shí)賦值武氓,在繞過(guò)已取消的前驅(qū)節(jié)點(diǎn)時(shí)進(jìn)行調(diào)整,在出隊(duì)時(shí)清零(為了GC)仇箱。 入隊(duì)操作直到附加后才賦值前驅(qū)節(jié)點(diǎn)的下一個(gè)字段县恕,因此看到 null 的下一個(gè)字段并不一定意味著該節(jié)點(diǎn)位于隊(duì)列末尾。 但是工碾,如果下一個(gè)字段顯示為空弱睦,則我們可以從尾部掃描上一個(gè)以進(jìn)行再次檢查百姓。 已取消節(jié)點(diǎn)的下一個(gè)字段設(shè)置為指向節(jié)點(diǎn)本身而不是null渊额,以使isOnSyncQueue的工作更輕松。


image
  • 使該節(jié)點(diǎn)入隊(duì)的線程垒拢。 在構(gòu)造時(shí)初始化旬迹,使用后消亡。
    image

在同步隊(duì)列中求类,nextWaiter 表示當(dāng)前節(jié)點(diǎn)是獨(dú)占模式還是共享模式
在條件隊(duì)列中奔垦,nextWaiter 表示下一個(gè)節(jié)點(diǎn)元素

鏈接到在條件隊(duì)列等待的下一個(gè)節(jié)點(diǎn),或者鏈接到特殊值SHARED尸疆。 由于條件隊(duì)列僅在以獨(dú)占模式保存時(shí)才被訪問椿猎,因此我們只需要一個(gè)簡(jiǎn)單的鏈接隊(duì)列即可在節(jié)點(diǎn)等待條件時(shí)保存節(jié)點(diǎn)。 然后將它們轉(zhuǎn)移到隊(duì)列中以重新獲取寿弱。 并且由于條件只能是獨(dú)占的犯眠,因此我們使用特殊值來(lái)表示共享模式來(lái)保存字段。

image

5 Condition 接口

JDK5 時(shí)提供症革。

  • 條件隊(duì)列 ConditionObject 實(shí)現(xiàn)了 Condition 接口


    image
  • 本節(jié)就讓我們一起來(lái)研究之


    image

Condition 將對(duì)象監(jiān)視方法(wait筐咧,notify和notifyAll)分解為不同的對(duì)象,從而通過(guò)與任意Lock實(shí)現(xiàn)結(jié)合使用噪矛,從而使每個(gè)對(duì)象具有多個(gè)wait-sets量蕊。 當(dāng) Lock 替換了 synchronized 方法和語(yǔ)句的使用,Condition 就可以替換了Object監(jiān)視器方法的使用艇挨。

Condition 的實(shí)現(xiàn)可以提供與 Object 監(jiān)視方法不同的行為和語(yǔ)義残炮,例如保證通知的順序,或者在執(zhí)行通知時(shí)不需要保持鎖定缩滨。 如果實(shí)現(xiàn)提供了這種專門的語(yǔ)義势就,則實(shí)現(xiàn)必須記錄這些語(yǔ)義辞居。

請(qǐng)注意,Condition實(shí)例只是普通對(duì)象蛋勺,它們本身可以用作 synchronized 語(yǔ)句中的目標(biāo)瓦灶,并且可以調(diào)用自己的監(jiān)視器 wait 和 notification 方法。 獲取 Condition 實(shí)例的監(jiān)視器鎖或使用其監(jiān)視器方法與獲取與該條件相關(guān)聯(lián)的鎖或使用其 await 和 signal 方法沒有特定的關(guān)系抱完。 建議避免混淆贼陶,除非可能在自己的實(shí)現(xiàn)中,否則不要以這種方式使用 Condition 實(shí)例巧娱。

 class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length)
         notFull.await();
       items[putptr] = x;
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0)
         notEmpty.await();
       Object x = items[takeptr];
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   }
 }

(ArrayBlockingQueue類提供了此功能碉怔,因此沒有理由實(shí)現(xiàn)此示例用法類。)
定義出一些方法禁添,這些方法奠定了條件隊(duì)列的基礎(chǔ)

API

await

  • 使當(dāng)前線程等待撮胧,直到被 signalled 或被中斷


    image

與此 Condition 相關(guān)聯(lián)的鎖被原子釋放,并且出于線程調(diào)度目的老翘,當(dāng)前線程被禁用芹啥,并且處于休眠狀態(tài),直到發(fā)生以下四種情況之一:

  • 其它線程為此 Condition 調(diào)用了 signal 方法铺峭,并且當(dāng)前線程恰好被選擇為要喚醒的線程
  • 其它線程為此 Condition 調(diào)用了 signalAll 方法
  • 其它線程中斷了當(dāng)前線程墓怀,并且當(dāng)前線程支持被中斷
  • 要么發(fā)生“虛假喚醒”。

在所有情況下卫键,在此方法可以返回之前傀履,必須重新獲取與此 Condition 關(guān)聯(lián)的鎖,才能真正被喚醒莉炉。當(dāng)線程返回時(shí)钓账,可以保證保持此鎖。

await 超時(shí)時(shí)間

  • 使當(dāng)前線程等待絮宁,直到被 signal 或中斷梆暮,或經(jīng)過(guò)指定的等待時(shí)間


    image

此方法在行為上等效于:

awaitNanos(unit.toNanos(time)) > 0

所以,雖然入?yún)⒖梢允侨我鈫挝坏臅r(shí)間羞福,但其實(shí)仍會(huì)轉(zhuǎn)化成納秒

awaitNanos

image

注意這里選擇納秒是為了避免計(jì)算剩余等待時(shí)間時(shí)的截?cái)嗾`差

signal()

  • 喚醒條件隊(duì)列中的一個(gè)線程惕蹄,在被喚醒前必須先獲得鎖


    image

signalAll()

  • 喚醒條件隊(duì)列中的所有線程


    image

6 鎖的獲取

獲取鎖顯式的方法就是 Lock.lock () ,最終目的其實(shí)是想讓線程獲得對(duì)資源的訪問權(quán)治专。而 Lock 又是 AQS 的子類卖陵,lock 方法根據(jù)情況一般會(huì)選擇調(diào)用 AQS 的 acquire 或 tryAcquire 方法。

acquire 方法 AQS 已經(jīng)實(shí)現(xiàn)了张峰,tryAcquire 方法是等待子類去實(shí)現(xiàn)泪蔫,acquire 方法制定了獲取鎖的框架,先嘗試使用 tryAcquire 方法獲取鎖喘批,獲取不到時(shí)撩荣,再入同步隊(duì)列中等待鎖铣揉。tryAcquire 方法 AQS 中直接拋出一個(gè)異常,表明需要子類去實(shí)現(xiàn)餐曹,子類可以根據(jù)同步器的 state 狀態(tài)來(lái)決定是否能夠獲得鎖逛拱,接下來(lái)我們?cè)敿?xì)看下 acquire 的源碼解析。

acquire 也分兩種台猴,一種是獨(dú)占鎖朽合,一種是共享鎖

6.1 acquire 獨(dú)占鎖

  • 獨(dú)占模式下,嘗試獲得鎖


    image

在獨(dú)占模式下獲取饱狂,忽略中斷曹步。 通過(guò)至少調(diào)用一次 tryAcquire(int) 來(lái)實(shí)現(xiàn),并在成功后返回休讳。 否則讲婚,將線程排隊(duì),并可能反復(fù)阻塞和解除阻塞俊柔,并調(diào)用 tryAcquire(int) 直到成功筹麸。 該方法可用于實(shí)現(xiàn)方法 Lock.lock()。
對(duì)于 arg 參數(shù)婆咸,該值會(huì)傳送給 tryAcquire竹捉,但不會(huì)被解釋,可以實(shí)現(xiàn)你喜歡的任何內(nèi)容尚骄。

  • 看一下 tryAcquire 方法
    image
    AQS 對(duì)其只是簡(jiǎn)單的實(shí)現(xiàn),具體獲取鎖的實(shí)現(xiàn)方法還是由各自的公平鎖和非公平鎖單獨(dú)實(shí)現(xiàn)侵续,實(shí)現(xiàn)思路一般都是 CAS 賦值 state 來(lái)決定是否能獲得鎖(閱讀后文的 ReentrantLock 核心源碼解析即可)倔丈。

執(zhí)行流程

  1. 嘗試執(zhí)行一次 tryAcquire
  • 成功直接返回
  • 失敗走 2
  1. 線程嘗試進(jìn)入同步隊(duì)列,首先調(diào)用 addWaiter 方法状蜗,把當(dāng)前線程放到同步隊(duì)列的隊(duì)尾

  2. 接著調(diào)用 acquireQueued 方法

  • 阻塞當(dāng)前節(jié)點(diǎn)
  • 節(jié)點(diǎn)被喚醒時(shí)需五,使其能夠獲得鎖
  1. 如果 2、3 失敗了轧坎,中斷線程

6.1.1 addWaiter

image

執(zhí)行流程

  1. 通過(guò)當(dāng)前的線程和鎖模式新建一個(gè)節(jié)點(diǎn)
  2. pred 指針指向尾節(jié)點(diǎn)tail
  3. 將Node 的 prev 指針指向 pred
  4. 通過(guò)compareAndSetTail方法宏邮,完成尾節(jié)點(diǎn)的設(shè)置。該方法主要是對(duì)tailOffset和Expect進(jìn)行比較缸血,如果tailOffset的Node和Expect的Node地址是相同的蜜氨,那么設(shè)置Tail的值為Update的值。


    image
  • 如果 pred 指針為 null(說(shuō)明等待隊(duì)列中沒有元素)捎泻,或者當(dāng)前 pred 指針和 tail 指向的位置不同(說(shuō)明被別的線程已經(jīng)修改)飒炎,就需要 enq


    image

    把新的節(jié)點(diǎn)添加到同步隊(duì)列的隊(duì)尾。

如果沒有被初始化笆豁,需要進(jìn)行初始化一個(gè)頭結(jié)點(diǎn)出來(lái)郎汪。但請(qǐng)注意赤赊,初始化的頭結(jié)點(diǎn)并不是當(dāng)前線程節(jié)點(diǎn),而是調(diào)用了無(wú)參構(gòu)造函數(shù)的節(jié)點(diǎn)煞赢。如果經(jīng)歷了初始化或者并發(fā)導(dǎo)致隊(duì)列中有元素抛计,則與之前的方法相同。其實(shí)照筑,addWaiter就是一個(gè)在雙端鏈表添加尾節(jié)點(diǎn)的操作爷辱,需要注意的是,雙端鏈表的頭結(jié)點(diǎn)是一個(gè)無(wú)參構(gòu)造函數(shù)的頭結(jié)點(diǎn)朦肘。

線程獲取鎖的時(shí)候饭弓,過(guò)程大體如下:

  1. 當(dāng)沒有線程獲取到鎖時(shí),線程1獲取鎖成功
  2. 線程2申請(qǐng)鎖媒抠,但是鎖被線程1占有


    image

    如果再有線程要獲取鎖弟断,依次在隊(duì)列中往后排隊(duì)即可。

在 addWaiter 方法中趴生,并沒有進(jìn)入方法后立馬就自旋阀趴,而是先嘗試一次追加到隊(duì)尾,如果失敗才自旋苍匆,因?yàn)榇蟛糠植僮骺赡芤淮尉蜁?huì)成功刘急,這種思路在自己寫自旋的時(shí)候可以多多參考哦。

6.1.2 acquireQueued

阻塞當(dāng)前線程浸踩。

  • 自旋使前驅(qū)結(jié)點(diǎn)的 waitStatus 變成 signal叔汁,然后阻塞自身
  • 獲得鎖的線程執(zhí)行完成后,釋放鎖時(shí)检碗,會(huì)喚醒阻塞的節(jié)點(diǎn)据块,之后再自旋嘗試獲得鎖
    final boolean acquireQueued(final Node node, int arg) {
        // 標(biāo)識(shí)是否成功取得資源
        boolean failed = true;
        try {
            // 標(biāo)識(shí)是否在等待過(guò)程被中斷過(guò)
            boolean interrupted = false;
            // 自旋,結(jié)果要么獲取鎖或者中斷
            for (;;) {
                // 獲取當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)
                final Node p = node.predecessor();
                // 若 p 是頭結(jié)點(diǎn)折剃,說(shuō)明當(dāng)前節(jié)點(diǎn)在真實(shí)數(shù)據(jù)隊(duì)列的首部另假,就嘗試獲取鎖(此前的頭結(jié)點(diǎn)還只是虛節(jié)點(diǎn))
                if (p == head && tryAcquire(arg)) {
                    // 獲取鎖成功,將頭指針移動(dòng)到當(dāng)前的 node
                    setHead(node);
                    p.next = null; // 輔助GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

看其中的具體方法:

setHead

image

方法的核心:

shouldParkAfterFailedAcquire

依據(jù)前驅(qū)節(jié)點(diǎn)的等待狀態(tài)判斷當(dāng)前線程是否應(yīng)該被阻塞

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        // 獲取頭結(jié)點(diǎn)的節(jié)點(diǎn)狀態(tài)
        int ws = pred.waitStatus;
        // 說(shuō)明頭結(jié)點(diǎn)處于喚醒狀態(tài)
        if (ws == Node.SIGNAL)
            /*
             * 該節(jié)點(diǎn)已經(jīng)設(shè)置了狀態(tài)怕犁,要求 release 以 signal边篮,以便可以安全park
             */
            return true;
        // 前文說(shuō)過(guò) waitStatus>0 是取消狀態(tài)    
        if (ws > 0) {
            /*
             * 跳過(guò)已被取消的前驅(qū)結(jié)點(diǎn)并重試
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus 必須為 0 或 PROPAGATE。 表示我們需要一個(gè) signal奏甫,但不要 park戈轿。 調(diào)用者將需要重試以確保在 park 之前還無(wú)法獲取。
             */
            // 設(shè)置前驅(qū)節(jié)點(diǎn)等待狀態(tài)為 SIGNAL 
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

為避免自旋導(dǎo)致過(guò)度消費(fèi) CPU 資源扶檐,以判斷前驅(qū)節(jié)點(diǎn)的狀態(tài)來(lái)決定是否掛起當(dāng)前線程

  • 掛起流程圖


    image

如下處理 prev 指針的代碼凶杖。shouldParkAfterFailedAcquire 是獲取鎖失敗的情況下才會(huì)執(zhí)行,進(jìn)入該方法后款筑,說(shuō)明共享資源已被獲取智蝠,當(dāng)前節(jié)點(diǎn)之前的節(jié)點(diǎn)都不會(huì)出現(xiàn)變化腾么,因此這個(gè)時(shí)候變更 prev 指針較安全。


image

parkAndCheckInterrupt

  • 將當(dāng)前線程掛起杈湾,阻塞調(diào)用棧并返回當(dāng)前線程的中斷狀態(tài)


    image
  • 一圖小結(jié)該方法流程


    image

    從上圖可以看出解虱,跳出當(dāng)前循環(huán)的條件是當(dāng)“前驅(qū)節(jié)點(diǎn)是頭結(jié)點(diǎn),且當(dāng)前線程獲取鎖成功”漆撞。

6.1.3 cancelAcquire

shouldParkAfterFailedAcquire中取消節(jié)點(diǎn)是怎么生成的呢殴泰?什么時(shí)候會(huì)把一個(gè)節(jié)點(diǎn)的waitStatus設(shè)置為-1?又是在什么時(shí)間釋放節(jié)點(diǎn)通知到被掛起的線程呢浮驳?來(lái)一起研究本小節(jié)源碼悍汛。


image
    private void cancelAcquire(Node node) {
        // 如果節(jié)點(diǎn)不存在,無(wú)視該方法
        if (node == null)
            return;
        // 設(shè)置該節(jié)點(diǎn)不關(guān)聯(lián)任何線程至会,即虛節(jié)點(diǎn)
        node.thread = null;

        // 跳過(guò)被取消的前驅(qū)結(jié)點(diǎn)們
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;


        // predNext 是要取消拼接的明顯節(jié)點(diǎn)离咐。如果沒有,以下情況 CAS 將失敗奉件,在這種情況下宵蛀,我們輸?shù)袅撕土硪粋€(gè)cancel或signal的競(jìng)爭(zhēng),因此無(wú)需采取進(jìn)一步措施县貌。
        // 通過(guò)前驅(qū)節(jié)點(diǎn)术陶,跳過(guò)取消狀態(tài)的node
        Node predNext = pred.next;

        // 這里可以使用無(wú)條件寫代替CAS,把當(dāng)前node的狀態(tài)設(shè)置為CANCELLED
        // 在這個(gè)原子步驟之后煤痕,其他節(jié)點(diǎn)可以跳過(guò)我們梧宫。
        // 在此之前,我們不受其他線程的干擾杭攻。
        node.waitStatus = Node.CANCELLED;

        // 如果是 tail 節(jié)點(diǎn), 移除自身
        // 如果當(dāng)前節(jié)點(diǎn)是尾節(jié)點(diǎn)祟敛,將從后往前的第一個(gè)非取消狀態(tài)的節(jié)點(diǎn)設(shè)置為尾節(jié)點(diǎn)
  // 更新失敗的話,則進(jìn)入else兆解,如果更新成功,將tail的后繼節(jié)點(diǎn)設(shè)置為null
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            // If successor needs signal, try to set pred's next-link
            // so it will get one. Otherwise wake it up to propagate.
            int ws;
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
                unparkSuccessor(node);
            }

            node.next = node; // 輔助 GC
        }
    }

當(dāng)前的流程:

  • 獲取當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)跑揉,如果前驅(qū)節(jié)點(diǎn)的狀態(tài)是CANCELLED锅睛,那就一直往前遍歷,找到第一個(gè)waitStatus <= 0的節(jié)點(diǎn)历谍,將找到的Pred節(jié)點(diǎn)和當(dāng)前Node關(guān)聯(lián)现拒,將當(dāng)前Node設(shè)置為CANCELLED
  • 根據(jù)當(dāng)前節(jié)點(diǎn)的位置望侈,考慮以下三種情況:
    (1) 當(dāng)前節(jié)點(diǎn)是尾節(jié)點(diǎn)印蔬。
    (2) 當(dāng)前節(jié)點(diǎn)是Head的后繼節(jié)點(diǎn)。
    (3) 當(dāng)前節(jié)點(diǎn)不是Head的后繼節(jié)點(diǎn)脱衙,也不是尾節(jié)點(diǎn)侥猬。

根據(jù)(2)例驹,來(lái)分析每一種情況的流程。

  • 當(dāng)前節(jié)點(diǎn)是尾節(jié)點(diǎn)


    image
  • 當(dāng)前節(jié)點(diǎn)是Head的后繼節(jié)點(diǎn)


    image
  • 當(dāng)前節(jié)點(diǎn)不是Head的后繼節(jié)點(diǎn)退唠,也不是尾節(jié)點(diǎn)
    image

    通過(guò)上面的流程鹃锈,我們對(duì)于CANCELLED節(jié)點(diǎn)狀態(tài)的產(chǎn)生和變化已經(jīng)有了大致的了解,但是為什么所有的變化都是對(duì)Next指針進(jìn)行了操作瞧预,而沒有對(duì)Prev指針進(jìn)行操作呢屎债?什么情況下會(huì)對(duì)Prev指針進(jìn)行操作?

執(zhí)行 cancelAcquire 時(shí)垢油,當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)可能已經(jīng)出隊(duì)(已經(jīng)執(zhí)行過(guò)try代碼塊中的shouldParkAfterFailedAcquire)盆驹,如果此時(shí)修改 prev 指針,有可能會(huì)導(dǎo)致 prev 指向另一個(gè)已經(jīng)出隊(duì)的 Node滩愁,因此這塊變化 prev 指針不安全躯喇。

6.2 tryAcquireNanos

嘗試以獨(dú)占模式獲取,如果中斷將中止惊楼,如果超過(guò)給定超時(shí)將直接失敗玖瘸。首先檢查中斷狀態(tài),然后至少調(diào)用一次#tryAcquire檀咙,成功后返回雅倒。否則,線程將排隊(duì)弧可,可能會(huì)反復(fù)地阻塞和取消阻塞蔑匣,調(diào)用#tryAcquire,直到成功或線程中斷或超時(shí)結(jié)束棕诵。此方法可用于實(shí)現(xiàn)方法 Lock#tryLock(long, TimeUnit)裁良。


image

嘗試性的獲取鎖, 獲取鎖不成功, 直接加入到同步隊(duì)列,加入操作即在doAcquireNanos

doAcquireNanos

以獨(dú)占限時(shí)模式獲取校套。

private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (nanosTimeout <= 0L)
        return false;
    // 截止時(shí)間    
    final long deadline = System.nanoTime() + nanosTimeout;
    // 將當(dāng)前的線程封裝成 Node 加入到同步對(duì)列里面
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            // 獲取當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)(當(dāng)一個(gè)n在同步對(duì)列里, 并且沒有獲取
            // lock 的 node 的前驅(qū)節(jié)點(diǎn)不可能是 null)
            final Node p = node.predecessor();
            // 判斷前驅(qū)節(jié)點(diǎn)是否為 head
            // 前驅(qū)節(jié)點(diǎn)是 head, 存在兩種情況 
            //  (1) 前驅(qū)節(jié)點(diǎn)現(xiàn)在持有鎖 
            //  (2) 前驅(qū)節(jié)點(diǎn)為 null, 已經(jīng)釋放鎖, node 現(xiàn)在可以獲取鎖
            // 則再調(diào)用 tryAcquire 嘗試獲取
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // 輔助GC
                failed = false;
                return true;
            }
            // 計(jì)算剩余時(shí)間
            nanosTimeout = deadline - System.nanoTime();
            // 超時(shí)价脾,直接返回 false
            if (nanosTimeout <= 0L)
                return false;
            // 調(diào)用 shouldParkAfterFailedAcquire 判斷是否需要阻塞
            if (shouldParkAfterFailedAcquire(p, node) &&
                // 若未超時(shí), 并且大于 spinForTimeoutThreshold, 則將線程掛起
                nanosTimeout > spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        // 在整個(gè)獲取中出錯(cuò)(中斷/超時(shí)等),則清除該節(jié)點(diǎn)
        if (failed)
            cancelAcquire(node);
    }
}

6.3 acquireSharedInterruptibly

  • 以共享模式獲取笛匙,如果中斷將中止侨把。


    image

    首先檢查中斷狀態(tài),然后至少調(diào)用一次 tryAcquireShared(int)妹孙,成功后返回秋柄。否則,線程將排隊(duì)蠢正,可能會(huì)反復(fù)阻塞和取消阻塞骇笔,調(diào)用 tryAcquireShared(int),直到成功或線程被中斷。

arg 參數(shù)笨触,這個(gè)值被傳遞給 tryAcquireShared(int)懦傍,但未被解釋,可以代表你喜歡的任何東西旭旭。如果當(dāng)前線程被中斷谎脯,則拋 InterruptedException。

doAcquireSharedInterruptibly

共享可中斷模式的獲取鎖

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    // 創(chuàng)建"當(dāng)前線程"的 Node 節(jié)點(diǎn)持寄,且其中記錄的共享鎖
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            // 獲取前驅(qū)節(jié)點(diǎn)
            final Node p = node.predecessor();
            // 如果前驅(qū)節(jié)點(diǎn)是頭節(jié)點(diǎn)
            if (p == head) {
                // 嘗試獲取鎖(由于前驅(qū)節(jié)點(diǎn)為頭節(jié)點(diǎn)源梭,所以可能此時(shí)前驅(qū)節(jié)點(diǎn)已經(jīng)成功獲取了鎖,所以嘗試獲取一下)
                int r = tryAcquireShared(arg);
                // 獲取鎖成功
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // 輔助 GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

7 鎖的釋放

7.1 release

以獨(dú)占模式釋放稍味。 如果 tryRelease 返回true废麻,則通過(guò)解鎖一個(gè)或多個(gè)線程來(lái)實(shí)現(xiàn)。此方法可用于實(shí)現(xiàn)方法 Lock#unlock

arg 參數(shù)將傳送到 tryRelease模庐,并且可以表示你自己喜歡的任何內(nèi)容烛愧。

  • 自定義實(shí)現(xiàn)的 tryRelease 如果返回 true,說(shuō)明該鎖沒有被任何線程持有

    image

  • 頭結(jié)點(diǎn)不為空并且頭結(jié)點(diǎn)的waitStatus不是初始化節(jié)點(diǎn)情況掂碱,解除線程掛起狀態(tài)


    image
  • h == null
    Head還沒初始化怜姿。初始時(shí) head == null,第一個(gè)節(jié)點(diǎn)入隊(duì)疼燥,Head會(huì)被初始化一個(gè)虛節(jié)點(diǎn)沧卢。所以說(shuō),這里如果還沒來(lái)得及入隊(duì)醉者,就會(huì)出現(xiàn)head == null

  • h != null && waitStatus == 0
    后繼節(jié)點(diǎn)對(duì)應(yīng)的線程仍在運(yùn)行中但狭,不需要喚醒

  • h != null && waitStatus < 0
    后繼節(jié)點(diǎn)可能被阻塞了,需要喚醒

unparkSuccessor

    private void unparkSuccessor(Node node) {
        /*
         * 如果狀態(tài)是負(fù)數(shù)的(即可能需要signal)撬即,請(qǐng)嘗試清除預(yù)期的signal立磁。 如果失敗或狀態(tài)被等待線程更改,則OK剥槐。
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * 要unpark的線程保留在后繼線程中唱歧,后者通常就是下一個(gè)節(jié)點(diǎn)。 但是粒竖,如果取消或顯然為空迈喉,從尾部逆向移動(dòng)以找到實(shí)際的未取消后繼者。
         */
        Node s = node.next;
        // 如果下個(gè)節(jié)點(diǎn)為 null 或者 cancelled温圆,就找到隊(duì)列最開始的非cancelled 的節(jié)點(diǎn)
        if (s == null || s.waitStatus > 0) {
            s = null;
            // 從尾部節(jié)點(diǎn)開始到隊(duì)首方向查找,尋得隊(duì)列第一個(gè) waitStatus<0 的節(jié)點(diǎn)孩革。
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        // 如果下個(gè)節(jié)點(diǎn)非空岁歉,而且unpark狀態(tài)<=0的節(jié)點(diǎn)
        if (s != null)
            LockSupport.unpark(s.thread);
    }
  • 之前的addWaiter方法的節(jié)點(diǎn)入隊(duì)并不是原子操作
    image

    標(biāo)識(shí)部分可以看做是 tail 入隊(duì)的原子操作,但是此時(shí)pred.next = node;尚未執(zhí)行,如果這個(gè)時(shí)候執(zhí)行了unparkSuccessor锅移,就無(wú)法從前往后找了
  • 在產(chǎn)生CANCELLED狀態(tài)節(jié)點(diǎn)的時(shí)候熔掺,先斷開的是 next 指針,prev 指針并未斷開非剃,因此也是必須要從后往前遍歷才能夠遍歷完

7.2 releaseShared

以共享模式釋放置逻。 如果 tryReleaseShared(int) 返回true,則通過(guò)解除一個(gè)或多個(gè)線程的阻塞來(lái)實(shí)現(xiàn)备绽。

arg 參數(shù) - 該值傳送給 tryReleaseShared(int)券坞,但并未實(shí)現(xiàn)万牺,可以自定義喜歡的任何內(nèi)容拾枣。


image

執(zhí)行流程

  1. tryReleaseShared 嘗試釋放共享鎖浸策,失敗返回 false粟誓,true 成功走2
  2. 喚醒當(dāng)前節(jié)點(diǎn)的后續(xù)阻塞節(jié)點(diǎn)

doReleaseShared

共享模式下的釋放動(dòng)作 - 表示后繼信號(hào)并確保傳播(注意:對(duì)于獨(dú)占模式噪沙,如果需要signal堕澄,釋放僅相當(dāng)于調(diào)用head的unparkSuccessor)秩贰。

    private void doReleaseShared() {
        /*
         * 即使有其他正在進(jìn)行的acquire/release螟深,也要確保 release 傳播塌西。 
         * 如果需要signal他挎,則以嘗試 unparkSuccessor head節(jié)點(diǎn)的常規(guī)方式進(jìn)行。
         * 但如果沒有捡需,則將狀態(tài)設(shè)置為 PROPAGATE办桨,以確保釋放后繼續(xù)傳播。
         * 此外栖忠,在執(zhí)行此操作時(shí)崔挖,必須循環(huán)以防添加新節(jié)點(diǎn)。 
         * 另外庵寞,與unparkSuccessor的其他用法不同狸相,我們需要知道CAS重置狀態(tài)是否失敗,如果重新檢查捐川,則失敗脓鹃。
         */
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // 循環(huán)以重新檢查
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // 在失敗的CAS上循環(huán)
            }
            if (h == head)                   // 如果頭結(jié)點(diǎn)變了則循環(huán)
                break;
        }
    }

8 中斷處理

喚醒對(duì)應(yīng)線程后,對(duì)應(yīng)的線程就會(huì)繼續(xù)往下執(zhí)行古沥。繼續(xù)執(zhí)行acquireQueued方法以后瘸右,中斷如何處理?

8.1 parkAndCheckInterrupt

park 的便捷方法岩齿,然后檢查是否中斷


image
  • 再看回 acquireQueued 代碼太颤,不論 parkAndCheckInterrupt 返回什么,都會(huì)執(zhí)行下次循環(huán)盹沈。若此時(shí)獲取鎖成功龄章,就返回當(dāng)前的 interrupted
    image
  • acquireQueued 為True,就會(huì)執(zhí)行 selfInterrupt
    image

8.2 selfInterrupt
image

該方法是為了中斷線程做裙。

獲取鎖后還要中斷線程的原因:

  • 當(dāng)中斷線程被喚醒時(shí)岗憋,并不知道被喚醒的原因,可能是當(dāng)前線程在等待中被中斷锚贱,也可能釋放鎖后被喚醒仔戈。因此通過(guò) Thread.interrupted() 檢查中斷標(biāo)識(shí)并記錄,如果發(fā)現(xiàn)該線程被中斷過(guò)拧廊,就再中斷一次
  • 線程在等待資源的過(guò)程中被喚醒监徘,喚醒后還是會(huì)不斷嘗試獲取鎖,直到搶到鎖卦绣。即在整個(gè)流程中耐量,并不響應(yīng)中斷,只是記錄中斷的記錄滤港。最后搶到鎖返回了廊蜒,那么如果被中斷過(guò)的話,就需要補(bǔ)充一次中斷

總結(jié)

AQS 的源碼實(shí)在是太多了溅漾,我們只研究核心源碼山叮,其他部分源碼都可以參考研究。
作為后面的讀寫等高級(jí)鎖的基礎(chǔ)添履,務(wù)必要搞懂 AQS 的設(shè)計(jì)哲學(xué)屁倔。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市暮胧,隨后出現(xiàn)的幾起案子锐借,更是在濱河造成了極大的恐慌,老刑警劉巖往衷,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钞翔,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡席舍,警方通過(guò)查閱死者的電腦和手機(jī)布轿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)来颤,“玉大人汰扭,你說(shuō)我怎么就攤上這事「GΓ” “怎么了萝毛?”我有些...
    開封第一講書人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)滑黔。 經(jīng)常有香客問我珊泳,道長(zhǎng)鲁冯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任色查,我火速辦了婚禮,結(jié)果婚禮上撞芍,老公的妹妹穿的比我還像新娘秧了。我一直安慰自己,他們只是感情好序无,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開白布验毡。 她就那樣靜靜地躺著,像睡著了一般帝嗡。 火紅的嫁衣襯著肌膚如雪晶通。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,337評(píng)論 1 310
  • 那天哟玷,我揣著相機(jī)與錄音狮辽,去河邊找鬼。 笑死巢寡,一個(gè)胖子當(dāng)著我的面吹牛喉脖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播抑月,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼树叽,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了谦絮?” 一聲冷哼從身側(cè)響起题诵,我...
    開封第一講書人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎层皱,沒想到半個(gè)月后性锭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡奶甘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年篷店,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片臭家。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡疲陕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出钉赁,到底是詐尸還是另有隱情蹄殃,我是刑警寧澤,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布你踩,位于F島的核電站诅岩,受9級(jí)特大地震影響讳苦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜吩谦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一鸳谜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧式廷,春花似錦咐扭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蠕趁,卻和暖如春薛闪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背俺陋。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工豁延, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人倔韭。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓术浪,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親寿酌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子胰苏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359