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)圖
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)字段担巩,但是就同步而言方援,僅跟蹤使用方法 getState,setState 和 compareAndSetState 操作的原子更新的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 繼承體系圖
4.2 定義
可知 AQS 是一個(gè)抽象類,生來(lái)就是被各種子類鎖繼承的汁胆。繼承自AbstractOwnableSynchronizer梭姓,其作用就是為了知道當(dāng)前是哪個(gè)線程獲得了鎖,便于后續(xù)的監(jiān)控
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)
注意等待狀態(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
在被轉(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的工作更輕松。
-
使該節(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)保存字段。
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
注意這里選擇納秒是為了避免計(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
執(zhí)行流程
- 嘗試執(zhí)行一次 tryAcquire
- 成功直接返回
- 失敗走 2
線程嘗試進(jìn)入同步隊(duì)列,首先調(diào)用 addWaiter 方法状蜗,把當(dāng)前線程放到同步隊(duì)列的隊(duì)尾
接著調(diào)用 acquireQueued 方法
- 阻塞當(dāng)前節(jié)點(diǎn)
- 節(jié)點(diǎn)被喚醒時(shí)需五,使其能夠獲得鎖
- 如果 2、3 失敗了轧坎,中斷線程
6.1.1 addWaiter
執(zhí)行流程
- 通過(guò)當(dāng)前的線程和鎖模式新建一個(gè)節(jié)點(diǎn)
- pred 指針指向尾節(jié)點(diǎn)tail
- 將Node 的 prev 指針指向 pred
-
通過(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ò)程大體如下:
- 當(dāng)沒有線程獲取到鎖時(shí),線程1獲取鎖成功
-
線程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
方法的核心:
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 指針較安全。
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é)源碼悍汛。
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)裁良。
嘗試性的獲取鎖, 獲取鎖不成功, 直接加入到同步隊(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 == nullh != 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)容拾枣。
執(zhí)行流程
- tryReleaseShared 嘗試釋放共享鎖浸策,失敗返回 false粟誓,true 成功走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 的便捷方法岩齿,然后檢查是否中斷
- 再看回 acquireQueued 代碼太颤,不論 parkAndCheckInterrupt 返回什么,都會(huì)執(zhí)行下次循環(huán)盹沈。若此時(shí)獲取鎖成功龄章,就返回當(dāng)前的 interrupted。
image -
acquireQueued 為True,就會(huì)執(zhí)行 selfInterruptimage
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é)屁倔。