1疆虚、為什么需要 AQS?AQS 的作用和重要性是什么满葛?
1.1 AQS 的重要性
先來介紹一下 AQS(AbstractQueuedSynchronizer)的重要性径簿,來看看 AQS 被用在了哪些類里面:
如圖所示,AQS 在 ReentrantLock嘀韧、ReentrantReadWriteLock篇亭、Semaphore、CountDownLatch锄贷、ThreadPoolExcutor 的 Worker 中都有運(yùn)用(JDK 1.8)译蒂,AQS 是這些類的底層原理。所以說 JUC 包里很多重要的工具類背后都離不開 AQS 框架谊却,因此 AQS 的重要性不言而喻柔昼。
1.2 學(xué)習(xí) AQS 框架的思路
對于學(xué)習(xí) AQS 的思路的理解。AQS 類的內(nèi)部結(jié)構(gòu)要比一般的類復(fù)雜得多炎辨,里面有很多細(xì)節(jié)捕透,不容易完全掌握,所以如果一上來就直接看源碼碴萧,容易把自己給繞暈乙嘀,容易陷入細(xì)節(jié)不能自拔,導(dǎo)致最后鎩羽而歸破喻。
其實(shí)我們大多數(shù)的程序員都是業(yè)務(wù)開發(fā)者虎谢,而不是 JDK 開發(fā)者,所以平時并不需要自己來開發(fā)類似于 ReentrantLock 這樣的工具類低缩,所以通常而言嘉冒,我們不會直接使用到 AQS 來進(jìn)行開發(fā)曹货,因?yàn)?JDK 已經(jīng)提供了很多封裝好的線程協(xié)作工具類,像ReentrantLock讳推、Semaphore 就是 JDK 提供給我們的顶籽,其內(nèi)部就用到了 AQS,而這些工具類已經(jīng)基本足夠覆蓋大部分的業(yè)務(wù)場景了银觅,這就使得我們即便不了解 AQS礼饱,也能利用這些工具類順利進(jìn)行開發(fā)。
既然學(xué)習(xí) AQS 的目的不是進(jìn)行代碼開發(fā)究驴,那為什么還需要學(xué)習(xí) AQS 呢镊绪?我認(rèn)為,學(xué)習(xí) AQS 的目的主要是想理解其背后的原理洒忧、學(xué)習(xí)設(shè)計思想蝴韭,以提高技術(shù)。
1.3 為什么需要 AQS熙侍?
ReentrantLock 榄鉴、Semaphore、CountDownLatch蛉抓、ReentrantReadWriteLock 等工具類都有類似的讓線程“協(xié)作”的功能庆尘,它們背后都是利用 AQS 來實(shí)現(xiàn)的。
它們有很多工作是類似的巷送,所以如果能把實(shí)現(xiàn)類似工作的代碼給提取出來驶忌,變成一個新的底層工具類(或稱為框架)的話,就可以直接使用這個工具類來構(gòu)建上層代碼了笑跛,而這個工具類其實(shí)就是 AQS付魔。
有了 AQS ,對于 ReentrantLock 和 Semaphore 等線程協(xié)作工具類而言堡牡,它們就不需要關(guān)心這么多的線程調(diào)度細(xì)節(jié)抒抬,只需要實(shí)現(xiàn)它們各自的設(shè)計邏輯即可。
如果沒有 AQS晤柄,那就需要每個線程協(xié)作工具類自己去實(shí)現(xiàn)至少以下內(nèi)容擦剑,包括:
- 狀態(tài)的原子性管理
- 線程的阻塞與解除阻塞
- 隊列的管理
這里的狀態(tài)對于不同的工具類而言,代表不同的含義芥颈,比如對于 ReentrantLock 而言惠勒,它需要維護(hù)鎖被重入的次數(shù),但是保存重入次數(shù)的變量是會被多線程同時操作的爬坑,就需要進(jìn)行處理纠屋,以便保證線程安全。不僅如此盾计,對于那些未搶到鎖的線程售担,還應(yīng)該讓它們陷入阻塞赁遗,并進(jìn)行排隊,并在合適的時機(jī)喚醒族铆。所以說這些內(nèi)容其實(shí)是比較繁瑣的岩四,而且也是比較重復(fù)的,而這些工作目前都由 AQS 來承擔(dān)了哥攘。
如果沒有 AQS剖煌,就需要 ReentrantLock 等類來自己實(shí)現(xiàn)相關(guān)的邏輯,但是讓每個線程協(xié)作工具類自己去正確并且高效地實(shí)現(xiàn)這些內(nèi)容逝淹,是相當(dāng)有難度的耕姊。AQS 可以幫我們把 “臟活累活” 都搞定,所以對于 ReentrantLock 和 Semaphore 等類而言栅葡,它們只需要關(guān)注自己特有的業(yè)務(wù)邏輯即可茉兰。
1.4 AQS 的作用
AQS 是一個用于構(gòu)建鎖、同步器等線程協(xié)作工具類的框架妥畏,有了 AQS 以后邦邦,很多用于線程協(xié)作的工具類就都可以很方便的被寫出來,有了 AQS 之后醉蚁,可以讓更上層的開發(fā)極大的減少工作量,避免重復(fù)造輪子鬼店,同時也避免了上層因處理不當(dāng)而導(dǎo)致的線程安全問題网棍,因?yàn)?AQS 把這些事情都做好了「局牵總之滥玷,有了 AQS 之后,構(gòu)建線程協(xié)作工具類就容易多了巍棱。
2惑畴、AQS 的內(nèi)部原理是什么樣的?
AQS 最核心的三大部分就是狀態(tài)航徙、隊列和期望協(xié)作工具類去實(shí)現(xiàn)的獲取/釋放等重要方法如贷。
2.1 state 狀態(tài)
如果 AQS 想要去管理或者想作為協(xié)作工具類的一個基礎(chǔ)框架,那么它必然要管理一些狀態(tài)到踏,而這個狀態(tài)在 AQS 內(nèi)部就是用 state 變量去表示的杠袱。定義如下:
/**
* The synchronization state.
*/
private volatile int state;
(1)state 的含義
state 的含義并不是一成不變的,它會根據(jù)具體實(shí)現(xiàn)類的作用不同而表示不同的含義窝稿,下面舉幾個例子楣富。
在信號量 Semaphore 里,state 表示的是剩余許可證的數(shù)量伴榔。如果最開始把 state 設(shè)置為 10纹蝴,這就代表許可證初始一共有 10 個庄萎,然后當(dāng)某一個線程取走一個許可證之后,這個 state 就會變?yōu)?9塘安,所以信號量的 state 相當(dāng)于是一個內(nèi)部計數(shù)器惨恭。
在 CountDownLatch 工具類里面,state 表示的是需要“倒數(shù)”的數(shù)量耙旦。一開始假設(shè)把它設(shè)置為 5脱羡,當(dāng)每次調(diào)用 CountDown 方法時,state 就會減 1免都,一直減到 0 的時候就代表這個門閂被放開锉罐。
在 ReentrantLock 中它表示的是鎖的占有情況。最開始是 0绕娘,表示沒有任何線程占有鎖脓规;如果 state 變成 1,則就代表這個鎖已經(jīng)被某一個線程所持有了险领。因?yàn)?ReentrantLock 是可重入的侨舆,同一個線程可以再次擁有這把鎖就叫重入。如果這個鎖被同一個線程多次獲取绢陌,那么 state 就會逐漸的往上加挨下,state 的值表示重入的次數(shù)。在釋放的時候也是逐步遞減脐湾,比如一開始是 4臭笆,釋放一次就變成了 3秤掌,只有當(dāng)它減到 0 的時候愁铺,此時恢復(fù)到最開始的狀態(tài)了,則代表現(xiàn)在沒有任何線程持有這個鎖了闻鉴。
(2)關(guān)于 state 修改的問題
因?yàn)?state 是會被多個線程共享的茵乱,會被并發(fā)地修改,所以所有去修改 state 的方法都必須要保證 state 是線程安全的孟岛∑拷撸可是 state 本身它僅僅是被 volatile 修飾的,volatile 本身并不足以保證線程安全蚀苛,所以就來看一下在验,AQS 在修改 state 的時候具體利用了什么樣的設(shè)計來保證并發(fā)安全。
舉兩個和 state 相關(guān)的方法堵未,分別是 compareAndSetState 及 setState腋舌,它們的實(shí)現(xiàn)已經(jīng)由 AQS 去完成了,直接調(diào)用這兩個方法就可以對 state 進(jìn)行線程安全的修改渗蟹。下面就來看一下這兩個方法的源碼是怎么實(shí)現(xiàn)的块饺。
- 先看一下 compareAndSetState 方法赞辩,是一個 CAS 操作,如下所示:
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
方法里面只有一行代碼授艰,它利用了 Unsafe 里面的 CAS 操作辨嗽,利用 CPU 指令的原子性保證了這個操作的原子性,與原子類去保證線程安全的原理是一致的淮腾。
- 接下來看一下 setState 方法的源碼糟需,如下所示:
protected final void setState(int newState) {
state = newState;
}
這里就要說到 volatile 的作用了,它適用于兩種場景谷朝,其中一種場景就是洲押,當(dāng)對基本類型的變量進(jìn)行直接賦值時,如果加了 volatile 就可以保證它的線程安全圆凰。注意杈帐,這是 volatile 的非常典型的使用場景。
/**
* The synchronization state.
*/
private volatile int state;
可以看出专钉,state 是 int 類型的挑童,屬于基本類型,并且這里的 setState 方法內(nèi)是對 state 直接賦值的跃须,它不涉及讀取之前的值站叼,也不涉及在原來值的基礎(chǔ)上再修改,所以僅僅利用 volatile 就可以保證在這種情況下的并發(fā)安全回怜,這就是 setState 方法線程安全的原因大年。
下面對 state 進(jìn)行總結(jié),在 AQS 中有 state 這樣的一個屬性玉雾,是被 volatile 修飾的,會被并發(fā)修改轻要,它代表當(dāng)前工具類的某種狀態(tài)复旬,在不同的類中代表不同的含義。
2.2 FIFO 隊列
FIFO 隊列冲泥,即先進(jìn)先出隊列驹碍,這個隊列最主要的作用是存儲等待的線程。假設(shè)很多線程都想要同時搶鎖凡恍,那么大部分的線程是搶不到的志秃,就得需要有一個隊列來存放、管理它們嚼酝。所以 AQS 的一大功能就是充當(dāng)線程的“排隊管理器”浮还。
當(dāng)多個線程去競爭同一把鎖的時候,就需要用排隊機(jī)制把那些沒能拿到鎖的線程串在一起闽巩;而當(dāng)前面的線程釋放鎖之后钧舌,這個管理器就會挑選一個合適的線程來嘗試搶剛剛釋放的那把鎖担汤。所以 AQS 就一直在維護(hù)這個隊列,并把等待的線程都放到隊列里面洼冻。
這個隊列內(nèi)部是雙向鏈表的形式崭歧,要想維護(hù)成一個線程安全的雙向隊列非常復(fù)雜,因?yàn)橐紤]很多的多線程并發(fā)問題撞牢。來看一下 AQS 作者 Doug Lea 給出的關(guān)于這個隊列的一個圖示:
在隊列中率碾,分別用 head 和 tail 來表示頭節(jié)點(diǎn)和尾節(jié)點(diǎn),兩者在初始化的時候都指向了一個空節(jié)點(diǎn)屋彪。頭節(jié)點(diǎn)可以理解為“當(dāng)前持有鎖的線程”所宰,而在頭節(jié)點(diǎn)之后的線程就被阻塞了,它們會等待被喚醒撼班,喚醒也是由 AQS 負(fù)責(zé)操作的歧匈。
2.3 獲取/釋放方法
獲取和釋放相關(guān)的重要方法,是協(xié)作工具類的邏輯的具體體現(xiàn)砰嘁,需要每一個協(xié)作工具類自己去實(shí)現(xiàn)件炉,所以在不同的工具類中,它們的實(shí)現(xiàn)和含義各不相同矮湘。
(1)獲取方法
獲取操作通常會依賴 state 變量的值斟冕,根據(jù) state 值不同,協(xié)作工具類也會有不同的邏輯缅阳,并且在獲取的時候也經(jīng)常會阻塞磕蛇,看幾個具體的例子。
- ReentrantLock 中的 lock 方法就是其中一個“獲取方法”十办,執(zhí)行時秀撇,如果發(fā)現(xiàn) state 不等于 0 且當(dāng)前線程不是持有鎖的線程,那么就代表這個鎖已經(jīng)被其他線程所持有了向族。這個時候呵燕,當(dāng)然就獲取不到鎖,于是就讓該線程進(jìn)入阻塞狀態(tài)件相。
- Semaphore 中的 acquire 方法就是其中一個“獲取方法”再扭,作用是獲取許可證,此時能不能獲取到這個許可證也取決于 state 的值夜矗。如果 state 值是正數(shù)泛范,那么代表還有剩余的許可證,數(shù)量足夠的話紊撕,就可以成功獲劝盏础;但如果 state 是 0,則代表已經(jīng)沒有更多的空余許可證了柠傍,此時這個線程就獲取不到許可證麸俘,會進(jìn)入阻塞狀態(tài)。
- CountDownLatch 獲取方法就是 await 方法(包含重載方法)惧笛,作用是“等待从媚,直到倒數(shù)結(jié)束”。執(zhí)行 await 的時候會判斷 state 的值患整,如果 state 不等于 0拜效,線程就陷入阻塞狀態(tài),直到其他線程執(zhí)行倒數(shù)方法把 state 減為 0各谚,此時就代表現(xiàn)在這個門閂放開了紧憾,所以之前阻塞的線程就會被喚醒。
(2)釋放方法
釋放方法是站在獲取方法的對立面的昌渤,通常和剛才的獲取方法配合使用赴穗。獲取方法可能會讓線程阻塞,比如說獲取不到鎖就會讓線程進(jìn)入阻塞狀態(tài)膀息,但是釋放方法通常是不會阻塞線程的般眉。在不同的實(shí)現(xiàn)類里面,對于 state 的操作是截然不同的潜支,需要由每一個協(xié)作類根據(jù)自己的邏輯去具體實(shí)現(xiàn)甸赃。
- 在 Semaphore 信號量里面,釋放就是 release 方法(包含重載方法)冗酿,release() 方法的作用是去釋放一個許可證埠对,會讓 state 加 1;
- 在 CountDownLatch 里面裁替,釋放就是 countDown 方法项玛,作用是倒數(shù)一個數(shù),讓 state 減 1弱判。
3稍计、AQS 的源碼分析
拓展資源:
- 第一個資源是 AQS 作者本人 Doug Lea 所寫的一篇論文,這篇論文自然是非常寶貴的學(xué)習(xí)資料裕循,請點(diǎn)擊這里查看;
- 第二個是來自 Javadoop 博客對于 AQS 的源碼分析的文章净刮,感興趣的話也可以閱讀剥哑,請點(diǎn)擊這里查看。
4淹父、AQS 在 CountDownLatch 等類中的應(yīng)用原理是什么株婴?
4.1 AQS 的用法
如果想使用 AQS 來寫一個自己的線程協(xié)作工具類,通常而言是分為以下三步,這也是 JDK 里利用 AQS 類的主要步驟:
第一步困介,新建一個自己的線程協(xié)作工具類大审,在內(nèi)部寫一個 Sync 類,該 Sync 類繼承 AbstractQueuedSynchronizer座哩,即 AQS徒扶;
第二步,想好設(shè)計的線程協(xié)作工具類的協(xié)作邏輯根穷,在 Sync 類里姜骡,根據(jù)是否是獨(dú)占,來重寫對應(yīng)的方法屿良。如果是獨(dú)占圈澈,則重寫 tryAcquire 和 tryRelease 等方法;如果是非獨(dú)占尘惧,則重寫 tryAcquireShared 和 tryReleaseShared 等方法康栈;
第三步,在自己的線程協(xié)作工具類中喷橙,實(shí)現(xiàn)獲取/釋放的相關(guān)方法啥么,并在里面調(diào)用 AQS 對應(yīng)的方法,如果是獨(dú)占則調(diào)用 acquire 或 release 等方法重慢,非獨(dú)占則調(diào)用 acquireShared 或 releaseShared 或 acquireSharedInterruptibly 等方法饥臂。
為什么要先繼承類,然后自己去判斷選擇哪些方法進(jìn)行重寫呢似踱?
因?yàn)槿绻菍?shí)現(xiàn)接口的話隅熙,那每一個抽象方法都需要實(shí)現(xiàn)。但實(shí)際上并不是 AQS 中的每個方法都需要重寫核芽,根據(jù)需求的不同囚戚,有選擇的去實(shí)現(xiàn)一部分就足以了,所以就設(shè)計為不采用實(shí)現(xiàn)接口轧简,而采用繼承類并重寫方法的形式驰坊。
繼承類后,是不強(qiáng)制要求重寫方法的哮独,所以如果一個方法都不重寫拳芙,行不行呢?答案是皮璧,如果不重寫剛才所講的 tryAcquire 等方法舟扎,是不行的,因?yàn)樵趫?zhí)行的時候會拋出異常悴务,看下 AQS 對這些方法的默認(rèn)的實(shí)現(xiàn)就知道了睹限。
下面有四個方法的代碼,分別是 tryAcquire、tryRelease羡疗、tryAcquireShared 和 tryReleaseShared 方法:
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
可以看到它們內(nèi)部只有一行實(shí)現(xiàn)代碼染服,就是直接拋出異常,所以要求在繼承 AQS 之后叨恨,必須把相關(guān)方法去重寫柳刮、覆蓋,這樣未來我們寫的線程協(xié)作類才能正常的運(yùn)行特碳。
4.2 AQS 在 CountDownLatch 的應(yīng)用
在 CountDownLatch 里面有一個子類诚亚,該類的類名叫 Sync,這個類正是繼承自 AQS午乓,而在 CountDownLatch 里面還有一個 sync 的變量站宗,正是 Sync 類的一個對象。下面給出了 CountDownLatch 部分代碼的截纫嬗:
public class CountDownLatch {
/**
* Synchronization control For CountDownLatch.
* Uses AQS state to represent count.
*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
private final Sync sync;
//省略其他代碼...
}
Sync 不但繼承了 AQS 類梢灭,而且還重寫了 tryAcquireShared 和 tryReleaseShared 方法。這里的 CountDownLatch 屬于非獨(dú)占的類型蒸其,因此它重寫了 tryAcquireShared 和 tryReleaseShared 方法敏释,那么這兩個方法的具體含義是什么呢?
接下來對 CountDownLatch 類里面最重要的 4 個方法進(jìn)行分析:
(1)構(gòu)造函數(shù)
首先來看看構(gòu)造函數(shù)摸袁。CountDownLatch 只有一個構(gòu)造方法钥顽,傳入的參數(shù)是需要“倒數(shù)”的次數(shù),每次調(diào)用 countDown 方法就會倒數(shù) 1靠汁,直到達(dá)到了最開始設(shè)定的次數(shù)之后蜂大,相當(dāng)于是“打開了門閂”,所以之前在等待的線程可以繼續(xù)工作了蝶怔。
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
從代碼中可以看到奶浦,當(dāng) count < 0 時會拋出異常,當(dāng) count > = 0踢星,即代碼 this.sync = new Sync( count ) 澳叉,往 Sync 中傳入了 count,這個里的 Sync 的構(gòu)造方法如下:
Sync(int count) {
setState(count);
}
該構(gòu)造函數(shù)調(diào)用了 AQS 的 setState 方法沐悦,并且把 count 傳進(jìn)去了成洗,而 setState 正是給 AQS 中的 state 變量賦值的,代碼如下:
protected final void setState(int newState) {
state = newState;
}
所以通過 CountDownLatch 構(gòu)造函數(shù)將傳入的 count 最終傳遞到 AQS 內(nèi)部的 state 變量藏否,給 state 賦值泌枪,state 就代表還需要倒數(shù)的次數(shù)。
(2)getCount
接下來介紹 getCount 方法秕岛,該方法的作用是獲取當(dāng)前剩余的還需要“倒數(shù)”的數(shù)量,getCount 方法的源碼如下:
public long getCount() {
return sync.getCount();
}
該方法 return 的是 sync 的 getCount:
int getCount() {
return getState();
}
getCount 方法調(diào)用的是 AQS 的 getState:
protected final int getState() {
return state;
}
如代碼所示,protected final int getState 方法直接 return 的就是 state 的值继薛,所以最終它獲取到的就在 AQS 中 state 變量的值修壕。
(3)countDown
再來看看 countDown 方法,該方法其實(shí)就是 CountDownLatch 的“釋放”方法遏考,下面來看下源碼:
public void countDown() {
sync.releaseShared(1);
}
在 countDown 方法中調(diào)用的是 sync 的 releaseShared 方法:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
可以看出慈鸠,releaseShared 先進(jìn)行 if 判斷,判斷 tryReleaseShared 方法的返回結(jié)果灌具,因此先把目光聚焦到 tryReleaseShared 方法中青团,tryReleaseShared 源碼如下所示 :
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
方法內(nèi)是一個 for 的死循環(huán),在循環(huán)體中咖楣,最開始是通過 getState 拿到當(dāng)前 state 的值并賦值給變量 c督笆,這個 c 可以理解為是 count 的縮寫,如果此時 c = 0诱贿,則意味著已經(jīng)倒數(shù)為零了娃肿,會直接會執(zhí)行下面的 return false 語句,一旦 tryReleaseShared 方法返回 false珠十,再往上看上一層的 releaseShared 方法料扰,就會直接跳過整個 if (tryReleaseShared(arg)) 代碼塊,直接返回 false焙蹭,相當(dāng)于 releaseShared 方法不產(chǎn)生效果晒杈,也就意味著 countDown 方法不產(chǎn)生效果。
再回到 tryReleaseShared 方法中往下看 return false 下面的語句孔厉,如果 c 不等于 0拯钻,在這里會先把 c-1 的值賦給 nextc,然后再利用 CAS 嘗試把 nextc 賦值到 state 上烟馅。如果賦值成功就代表本次 countDown 方法操作成功说庭,也就意味著把 AQS 內(nèi)部的 state 值減了 1。最后郑趁,是 return nextc == 0刊驴,如果 nextc 為 0,意味著本次倒數(shù)后恰好達(dá)到了規(guī)定的倒數(shù)次數(shù)寡润,門閂應(yīng)當(dāng)在此時打開捆憎,所以 tryReleaseShared 方法會返回 true,那么再回到之前的 releaseShared 方法中梭纹,可以看到躲惰,接下來會調(diào)用 doReleaseShared 方法,效果是對之前阻塞的線程進(jìn)行喚醒变抽,讓它們繼續(xù)執(zhí)行础拨。
如果結(jié)合具體的數(shù)來分析氮块,可能會更清晰。假設(shè) c = 2诡宗,則代表需要倒數(shù)的值是 2滔蝉,nextc = c-1,所以 nextc 就是 1塔沃,然后利用 CAS 嘗試把 state 設(shè)置為 1蝠引,假設(shè)設(shè)置成功,最后會 return nextc == 0蛀柴,此時 nextc 等于 1螃概,不等于 0,所以返回 false鸽疾,也就意味著 countDown 之后成功修改了 state 的值吊洼,把它減 1 了,但并沒有喚醒線程肮韧。
下一次執(zhí)行 countDown時融蹂,c 的值就是 1,而 nextc = c - 1弄企,所以 nextc 等于 0超燃,若這時 CAS 操作成功,最后 return nextc == 0拘领,所以方法返回 true意乓,一旦 tryReleaseShared 方法 return true,則 releaseShared 方法會調(diào)用 doReleaseShared 方法约素,把所有之前阻塞的線程都喚醒届良。
(4)await
接著來看看 await 方法,該方法是 CountDownLatch 的“獲取”方法圣猎,調(diào)用 await 方法會把線程阻塞士葫,直到倒數(shù)為 0 才能繼續(xù)執(zhí)行。await 方法和 countDown 是配對的送悔,追蹤源碼可以看到 await 方法的實(shí)現(xiàn):
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
它會調(diào)用 sync 的 acquireSharedInterruptibly 慢显,并且傳入 1。acquireSharedInterruptibly 方法源碼如下所示:
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
可以看到欠啤,它除了對于中斷的處理之外荚藻,比較重要的就是 tryAcquireShared 方法。這個方法很簡單洁段,它會直接判斷 getState 的值是不是等于 0应狱,如果等于 0 就返回 1,不等于 0 則返回 -1祠丝。
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
getState 方法獲取到的值是剩余需要倒數(shù)的次數(shù)疾呻,如果此時剩余倒數(shù)的次數(shù)大于 0除嘹,那么 getState 的返回值自然不等于 0,因此 tryAcquireShared 方法會返回 -1罐韩,一旦返回 -1憾赁,再看到 if (tryAcquireShared(arg) < 0) 語句中,就會符合 if 的判斷條件散吵,并且去執(zhí)行 doAcquireSharedInterruptibly 方法,然后會讓線程進(jìn)入阻塞狀態(tài)蟆肆。
看下另一種情況矾睦,當(dāng) state 如果此時已經(jīng)等于 0 了,那就意味著倒數(shù)其實(shí)結(jié)束了炎功,不需要再去等待了枚冗,就是說門閂是打開狀態(tài),所以說此時 getState 返回 0蛇损,tryAcquireShared 方法返回 1 赁温,一旦返回 1,對于 acquireSharedInterruptibly 方法而言相當(dāng)于立刻返回淤齐,也就意味著 await 方法會立刻返回股囊,那么此時線程就不會進(jìn)入阻塞狀態(tài)了,相當(dāng)于倒數(shù)已經(jīng)結(jié)束更啄,立刻放行了稚疹。
AQS 在 CountDownLatch 的應(yīng)用總結(jié)
當(dāng)線程調(diào)用 CountDownLatch 的 await 方法時,便會嘗試獲取“共享鎖”祭务,不過一開始通常獲取不到鎖内狗,于是線程被阻塞∫遄叮“共享鎖”可獲取到的條件是“鎖計數(shù)器”的值為 0柳沙,而“鎖計數(shù)器”的初始值為 count,當(dāng)每次調(diào)用 CountDownLatch 對象的 countDown 方法時拌倍,也可以把“鎖計數(shù)器” -1赂鲤。通過這種方式,調(diào)用 count 次 countDown 方法之后贰拿,“鎖計數(shù)器”就為 0 了蛤袒,于是之前等待的線程就會繼續(xù)運(yùn)行了,并且此時如果再有線程想調(diào)用 await 方法時也會被立刻放行膨更,不會再去做任何阻塞操作了妙真。