AbstractQueuedSynchronizer
??提供一個(gè)框架皆串,用于實(shí)現(xiàn)依賴先進(jìn)先出(FIFO)等待隊(duì)列的阻塞鎖和相關(guān)同步器(信號(hào)量惰聂、事件等)异剥。該類被設(shè)計(jì)為大多數(shù)依賴于單個(gè)原子int
值來表示狀態(tài)的同步器的有用基礎(chǔ)桅狠。子類必須定義改變?cè)摖顟B(tài)的受保護(hù)方法潮瓶,以及這些方法定義了該狀態(tài)在獲取或釋放該對(duì)象方面的含義蚕泽∩卫妫考慮到這些,該類中的其他方法執(zhí)行所有隊(duì)列和阻塞機(jī)制须妻。子類可以維護(hù)其他狀態(tài)字段仔蝌,但是只有使用方法getState
、setState
和compareAndSetState
對(duì)原子更新的int
值進(jìn)行同步跟蹤荒吏。
??子類應(yīng)該被定義為非公共的內(nèi)部幫助類敛惊,用于實(shí)現(xiàn)其封閉類的同步屬性。類AbstractQueuedSynchronizer
不實(shí)現(xiàn)任何同步接口绰更。相反瞧挤,它定義了諸如acquireInterruptibly
這樣的方法锡宋,具體鎖和相關(guān)同步器可以根據(jù)需要調(diào)用這些方法來實(shí)現(xiàn)它們的公共方法。
??這個(gè)類支持默認(rèn)的模式和模式特恬。在獨(dú)占模式下獲取時(shí)执俩,其他線程試圖獲取的操作無法成功。多個(gè)線程獲取的共享模式可能(但不需要)成功癌刽。這個(gè)類不“明白”除了機(jī)械意義上的差異之外役首,這些差異還包括:當(dāng)共享模式獲取成功時(shí),下一個(gè)等待的線程(如果存在的話)也必須確定它是否能夠獲取妒穴。在不同模式下等待的線程共享同一個(gè)FIFO隊(duì)列宋税。通常,實(shí)現(xiàn)子類只支持其中一種模式讼油,但這兩種模式都可以發(fā)揮作用杰赛,例如在ReadWriteLock
中。只支持獨(dú)占模式或僅支持共享模式的子類不需要定義支持未使用模式的方法矮台。
??這個(gè)類定義了一個(gè)嵌套的ConditionObject
類,可以用作Condition
由子類實(shí)現(xiàn)支持獨(dú)占模式的方法isHeldExclusively
報(bào)告同步是否只對(duì)當(dāng)前線程持有,release
方法調(diào)用與getState
獲取當(dāng)前的值和acquire
方法完全釋放這個(gè)對(duì)象,鑒于這個(gè)保存的狀態(tài)值,最終將該對(duì)象恢復(fù)到以前獲取的狀態(tài)乏屯。AbstractQueuedSynchronizer
沒有方法會(huì)創(chuàng)建這樣的條件,所以如果不能滿足這個(gè)約束瘦赫,就不要使用它辰晕。ConditionObject
的行為當(dāng)然依賴于它的同步器實(shí)現(xiàn)的語義。
??該類為內(nèi)部隊(duì)列提供了檢查确虱、檢測(cè)和監(jiān)視方法含友,跟Condition
對(duì)象的方法很相似⌒1纾可以使用AbstractQueuedSynchronizer
將這些同步機(jī)制按照需要導(dǎo)出到類中窘问。
??該類的序列化只存儲(chǔ)底層原子整數(shù)維護(hù)狀態(tài),因此反序列化的對(duì)象具有空線程隊(duì)列宜咒。需要序列化的典型子類將定義一個(gè)readObject
方法惠赫,該方法在反序列化時(shí)將其恢復(fù)到已知的初始狀態(tài)。
使用
??如果要使用這個(gè)類作為同步器的基礎(chǔ)故黑,可以使用getState
儿咱、setState
和/或compareAndSetState
檢查和/或修改同步狀態(tài),根據(jù)需要重新定義以下方法:
?--tryAcquire
?--tryRelease
?--tryAcquireShared
?--tryReleaseShared
?--isHeldExclusively
??默認(rèn)情況下,每個(gè)方法都會(huì)UnsupportedOperationException
场晶。這些方法的實(shí)現(xiàn)必須是內(nèi)部線程安全的混埠,并且通常應(yīng)該是短的,而不是阻塞的诗轻。定義這些方法是使用這個(gè)類的only支持的方法钳宪。所有其他方法都聲明為final
,因?yàn)樗鼈儾荒塥?dú)立更改。
??您還可能發(fā)現(xiàn)從AbstractOwnableSynchronizer
繼承的方法對(duì)于跟蹤擁有獨(dú)占同步器的線程非常有用使套。我們鼓勵(lì)您使用它們——這使得監(jiān)視和診斷工具能夠幫助用戶確定哪些線程持有鎖。
??即使這個(gè)類基于內(nèi)部FIFO隊(duì)列鞠柄,它也不會(huì)自動(dòng)執(zhí)行FIFO獲取策略侦高。獨(dú)占同步的核心形式為:
Acquire:
while (!tryAcquire(arg)) {
//enqueue thread if it is not already queued;
//possibly block current thread;
}
Release:
if (tryRelease(arg))
//unblock the first queued thread;
(共享模式類似,但可能涉及級(jí)聯(lián)信號(hào)厌杜。)
??因?yàn)楹炄氆@取是在排隊(duì)之前調(diào)用的奉呛,所以一個(gè)新的獲取線程可能會(huì)“插入”在其他被阻塞和排隊(duì)的線程之前。但是夯尽,如果需要瞧壮,您可以通過內(nèi)部調(diào)用一個(gè)或多個(gè)檢查方法來定義tryAcquire
和/或tryAcquireShared
來禁止插入,從而提供一個(gè)"公平" FIFO獲取順序匙握。特別是咆槽,如果hasQueuedPredecessors
(一個(gè)專門為公平同步器設(shè)計(jì)的方法)返回true
,那么大多數(shù)公平同步器可以定義tryAcquire
來返回false
圈纺。其他變化是可能的秦忿。
??對(duì)于默認(rèn)的barging(也稱為"貪心")策略,吞吐量和可伸縮性通常是最高的蛾娶。雖然不能保證這是公平的或無中斷的灯谣,但允許較早排隊(duì)的線程在較晚排隊(duì)的線程之前重新競(jìng)爭(zhēng),而且每次重新競(jìng)爭(zhēng)都有公平的機(jī)會(huì)面對(duì)傳入成功的線程蛔琅。而且胎许,在獲得的同時(shí)不<轉(zhuǎn)動(dòng)>。通常情況下罗售,在阻塞之前辜窑,它們可以執(zhí)行tryAcquire
的多次調(diào)用,其間穿插著其他計(jì)算莽囤。當(dāng)獨(dú)占同步只被短暫地持有時(shí)谬擦,這就提供了自旋的大部分好處,而當(dāng)它不被持有時(shí)朽缎,就沒有了大部分的不利因素惨远。如果需要,您可以通過前面的調(diào)用來增強(qiáng)這種能力话肖,通過“fast-path”檢查來獲取方法北秽,可能會(huì)預(yù)先檢查hasContended
和/或hasQueuedThreads
,只在同步器可能不存在競(jìng)爭(zhēng)的情況下才能這樣做最筒。
??這個(gè)類為同步提供了一個(gè)高效且可伸縮的基礎(chǔ)贺氓,部分原因是它將同步器的使用范圍專門化為可以依賴于int
狀態(tài)、獲取和釋放參數(shù)以及內(nèi)部FIFO等待隊(duì)列的同步器床蜘。當(dāng)這還不夠時(shí)辙培,您可以使用java.util.concurrent.atomic
類蔑水、自定義的java.util.Queue
類和LockSupport
阻塞支持,從較低的級(jí)別構(gòu)建同步器扬蕊。
使用舉例
??這是一個(gè)不可重入互斥鎖類搀别,它使用0表示未鎖狀態(tài),1表示已鎖狀態(tài)尾抑。雖然不可重入鎖并不嚴(yán)格要求記錄當(dāng)前的持有線程歇父,但是這個(gè)類還是這樣做了,以便更容易地監(jiān)視使用情況再愈。它還支持conditions
榜苫,并公開了一種檢測(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;
}
// 如果狀態(tài)為0,則獲取鎖
public boolean tryAcquire(int acquires) {
assert acquires == 1; // Otherwise unused
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 通過把狀態(tài)設(shè)為0來釋放鎖
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); // reset to unlocked state
}
// sync對(duì)象完成所有的工作翎冲。我們只需要用它轉(zhuǎn)發(fā)功能就行垂睬。
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è)類似于java.util.concurrent.CountDownLatch
的鎖存器類。區(qū)別是它只需要一個(gè)signal
來觸發(fā)府适。因?yàn)殒i存器是非獨(dú)占的羔飞,它使用shared
獲取和釋放方法。
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);
}
}
AbstractQueuedSynchronizer.Node
??等待隊(duì)列節(jié)點(diǎn)類檐春。
??等待隊(duì)列是CLH
(Craig逻淌、Landin和Hagersten)鎖隊(duì)列的變體。CLH
鎖通常用于自旋鎖疟暖。相反卡儒,我們使用它們來阻塞同步器,但使用相同的基本策略俐巴,即在其節(jié)點(diǎn)的前身中保存關(guān)于線程的一些控制信息骨望。每個(gè)節(jié)點(diǎn)中的status
字段跟蹤線程是否應(yīng)該阻塞。一個(gè)節(jié)點(diǎn)在其前驅(qū)節(jié)點(diǎn)釋放時(shí)發(fā)出信號(hào)欣舵。否則擎鸠,隊(duì)列的每個(gè)節(jié)點(diǎn)都充當(dāng)持有單個(gè)等待線程的特定通知樣式的監(jiān)視器。狀態(tài)字段不控制線程是否被授予鎖等缘圈。如果線程是隊(duì)列中的第一個(gè)線程劣光,它可以嘗試獲取。但第一個(gè)也并不能保證成功糟把,它只給你競(jìng)爭(zhēng)的權(quán)利绢涡。因此,當(dāng)前發(fā)布的競(jìng)爭(zhēng)者線程可能需要重新等待遣疯。
??要入隊(duì)一個(gè)CLH鎖雄可,您需要將其原子拼接為新尾部。 要出列,您只需設(shè)置head字段数苫。
+------+ prev +-----+ +-----+
head | | <---- | | <---- | | tail
+------+ +-----+ +-----+
??插入CLH隊(duì)列只需要對(duì)“尾部”進(jìn)行單個(gè)原子操作聪舒,因此存在從未排隊(duì)到排隊(duì)的簡(jiǎn)單原子點(diǎn)劃分。 同樣虐急,出列只涉及更新“頭部”过椎。 但是,節(jié)點(diǎn)需要更多的工作來確定他們的后驅(qū)是誰戏仓,部分是為了處理由于超時(shí)和中斷而可能的取消。
??處理取消主要需要“prev”鏈接(未在原始CLH鎖中使用)亡鼠。 如果節(jié)點(diǎn)被取消赏殃,則其后驅(qū)(通常)重新鏈接到未取消的前驅(qū)。 有關(guān)自旋鎖的類似機(jī)制的解釋间涵,請(qǐng)參閱Scott和Scherer的論文
http://www.cs.rochester.edu/u/scott/synchronization/
??我們還使用next
鏈接來實(shí)現(xiàn)阻塞機(jī)制仁热。 每個(gè)節(jié)點(diǎn)保存了自己所在線程ID,因此前驅(qū)通過遍歷下一個(gè)鏈接來通知下一個(gè)節(jié)點(diǎn)以確定它是哪個(gè)線程勾哩。 后驅(qū)的確定必須避免使用新排隊(duì)節(jié)點(diǎn)的競(jìng)爭(zhēng)來設(shè)置其前驅(qū)的next
字段抗蠢。 必要時(shí),當(dāng)節(jié)點(diǎn)的后驅(qū)看起來為空時(shí)思劳,通過從原子更新的tail
向后檢查來解決這個(gè)問題迅矛。 (或者,換句話說潜叛,下一個(gè)鏈接是一個(gè)優(yōu)化秽褒,因此我們通常不需要向后掃描。)
??消除為基本算法引入了一些保守性威兜。 由于我們必須輪詢消除其他節(jié)點(diǎn)销斟,我們可能會(huì)忽略已消除的節(jié)點(diǎn)是在我們前面還是在我們后面。 這一點(diǎn)通過消除時(shí)始終取消停車的繼承人來處理椒舵,允許他們穩(wěn)定在新的前驅(qū)蚂踊,除非我們能夠確定一位將承擔(dān)此責(zé)任的未經(jīng)解除的前驅(qū)。
??CLH隊(duì)列需要一個(gè)虛擬標(biāo)頭節(jié)點(diǎn)才能開始笔宿。 但是我們不會(huì)在構(gòu)造函數(shù)里創(chuàng)建它們犁钟,因?yàn)槿绻麤]有調(diào)用就會(huì)浪費(fèi)資源。 相反措伐,節(jié)點(diǎn)在第一次調(diào)用時(shí)被構(gòu)造特纤,并且設(shè)置頭尾指針。
??Conditions
等待的線程使用相同的節(jié)點(diǎn)侥加,但使用不同鏈接捧存。Conditions
只需要鏈接簡(jiǎn)單(非并發(fā))鏈接隊(duì)列中的節(jié)點(diǎn),因?yàn)樗鼈儍H在完全持有時(shí)才被訪問。 等待時(shí)昔穴,將節(jié)點(diǎn)插入條件隊(duì)列镰官。 根據(jù)信號(hào),節(jié)點(diǎn)被轉(zhuǎn)移到主隊(duì)列吗货。 狀態(tài)字段的特殊值用于標(biāo)記節(jié)點(diǎn)所在的隊(duì)列泳唠。
??感謝Dave Dice,Mark Moir宙搬,Victor Luchangco笨腥,Bill Scherer和Michael Scott以及JSR-166專家組成員對(duì)本課程設(shè)計(jì)的有益想法,討論和批評(píng)勇垛。