天天看點

CountDownLatch源碼閱讀

CountDownLatch源碼閱讀

結合AQS,CountDownLatch源碼解析

<code>CountDownLatch</code>是JUC提供的一個線程同步工具,主要功能就是協調多個線程之間的同步,或者說實作線程之間的通信

CountDown,數數字,隻能往下數。Latch,門闩。光看名字就能明白這個<code>CountDownLatch</code>是如何使用的了哈哈。<code>CountDownLatch</code>就相當于一個計數器,計數器的初值通過構造方法的參數來設定。調用<code>CountDownLatch</code>執行個體的<code>await</code>方法的線程,會等待計數器變為0才會被喚醒,繼續向下執行。那麼計數器如何變為0呢?

如果其他線程調用該<code>CountDownLatch</code>執行個體的<code>countDown</code>方法,會将計數值減1。當減為0時,會讓那些因調用<code>await</code>方法而阻塞等待的線程繼續執行。這樣就實作了這些線程之間的同步功能

作者:酒冽        出處:https://www.cnblogs.com/frankiedyz/p/15730573.html

版權:本文版權歸作者和部落格園共有

轉載:歡迎轉載,但未經作者同意,必須保留此段聲明;必須在文章中給出原文連接配接;否則必究法律責任

直接介紹或許太抽象,對于初學者來說很難了解,最好的方式就是通過一個實際場景來引入

有一種通用的場景:主線程開啟多個子線程去并行執行多個子任務,等待所有子線程執行完畢,主線程收集子線程的執行結果并統計

比如,主線程等A和B給它轉賬,等收齊所有錢再一并放入銀行賺利息。示例代碼如下:

指令行輸出如下:

點選檢視代碼

得益于<code>CountDownLatch</code>的同步功能,當上述代碼執行結束時,<code>money</code>的值必定是120,而不會是0、60或140。因為主線程會一緻<code>await</code>直到線程A和線程B都執行完<code>latch.countDown()</code>才會繼續往下執行

<code>CountDownLatch</code>的實作原理其實就是<code>AbstractQueuedSynchronizer</code>(AQS)

<code>CountDownLatch</code>有一個内部類<code>Sync</code>,它實作了AQS類定義的部分鈎子方法,<code>CountDownLatch</code>通過<code>Sync</code>類執行個體<code>sync</code>實作了所有功能,調用<code>CountDownLatch</code>的方法都會委托給<code>sync</code>域來執行

是以,要搞懂<code>CountDownLatch</code>,必須搞懂AQS以及<code>Sync</code>類。接下來就跟我一起來剖析一下源碼,看看這個<code>Sync</code>到底幹了些啥

<code>CountDownLatch</code>的構造函數中可以傳入<code>count</code>參數,表明必須調用<code>count</code>次<code>countDown</code>方法,才能讓調用<code>await</code>的線程繼續向下執行。其源碼如下:

實際上是初始化了一個<code>Sync</code>類對象,并注入到<code>CountDownLatch</code>的<code>sync</code>域中。<code>Sync</code>構造方法如下:

<code>Sync</code>構造方法實際上就是設定了AQS中的<code>state</code>,将其設定為初始計數值為<code>count</code>

重要結論:AQS的<code>state</code>就表示<code>CountDownLatch</code>目前的計數值

<code>await</code>方法是一個執行個體方法,調用它的線程一直阻塞等待,直到<code>CountDownLatch</code>對象的計數值降為0,才能被喚醒。如果調用<code>await</code>時計數值就已經是0,就不會被阻塞

<code>await</code>方法是響應中斷的:

如果一個線程在調用<code>await</code>方法之前就已經被中斷,那麼調用時會直接抛出中斷異常

如果一個線程調用<code>await</code>方法阻塞等待過程中,收到中斷信号,就會抛出中斷異常

說了這麼多,還是來看看<code>await</code>的源碼吧:

可以看到,這個方法實際上是委托給了<code>Sync</code>類對象<code>sync</code>來執行,這裡的<code>acquireSharedInterruptibly</code>已經由<code>Sync</code>類的父類AQS提供了實作,源碼如下:

這段代碼在 全網最詳細的AbstractQueuedSynchronizer(AQS)源碼剖析 系列中有詳細介紹,不過那裡并沒有涉及到具體的應用類(如<code>CountDownLatch</code>這種),隻是高屋建瓴地分析過,這裡正好借助<code>CountDownLatch</code>來更好地了解它

從<code>acquireSharedInterruptibly</code>源碼中可以看到,如果線程在調用<code>await</code>之前就已經被設定了中斷狀态,那麼會直接抛出<code>InterruptedException</code>異常

該方法接下來會調用鈎子方法<code>tryAcquireShared</code>,<code>Sync</code>類為該方法提供了具體實作,源碼如下:

AQS雖然沒有為<code>tryAcquireShared</code>提供具體實作,但是規定了傳回值的含義:

負數:表明擷取失敗,該線程需要被加入同步隊列阻塞等待

0:表明擷取共享資源成功,但是後續擷取共享資源一定不會成功

正數:表明擷取共享資源成功,而且後續的擷取也可能成功

讓我們來分析一下<code>Sync</code>類實作的<code>tryAcquireShared</code>方法:

如果<code>state</code>不為0,即<code>CountDownLatch</code>對象的計數值還沒減到0,則傳回-1,會繼續執行<code>doAcquireSharedInterruptibly</code>方法,将調用<code>await</code>的線程加入同步隊列阻塞等待

如果<code>state</code>為0,即<code>CountDownLatch</code>對象的計數值已經減為0,則傳回1,調用<code>await</code>會直接傳回,不會被阻塞

注:<code>doAcquireSharedInterruptibly</code>的具體分析見 全網最詳細的AbstractQueuedSynchronizer(AQS)源碼剖析 系列

<code>countDown</code>方法也是執行個體方法,調用它會将<code>CountDownLatch</code>對象的計數值減1。如果正好減為0,那麼會将所有因調用<code>await</code>而被阻塞的線程都喚醒。其源碼如下:

該方法實際上委托給<code>sync</code>來執行,這裡的<code>releaseShared</code>方法在<code>Sync</code>類的父類AQS中提供了具體實作,其源碼如下:

該方法首先會調用鈎子方法<code>tryReleaseShared</code>,該方法在<code>Sync</code>類中提供了具體實作,源碼如下:

AQS雖然沒有為<code>tryReleaseShared</code>提供具體實作,但是規定了傳回值的含義:

true:此次釋放資源的行為可能會讓一個阻塞等待中的線程被喚醒

false:otherwise

讓我們來分析一下<code>Sync</code>類實作的<code>tryReleaseShared</code>方法:

該方法所有代碼都包含在一個<code>for</code>循環中,這是為了應對CAS失敗的情況。循環體内CAS修改<code>state</code>,即将<code>CountDownLatch</code>的計數值減1

如果<code>CountDownLatch</code>的計數值減1後變成0,則傳回true。那麼<code>releaseShared</code>方法會繼續調用<code>doReleaseShared</code>方法,喚醒同步隊列中的後續線程

如果不為0,則傳回false,無事發生~

注:<code>doReleaseShared</code>方法的作用是喚醒隊首線程,并確定狀态傳播,該方法的詳細解釋見 全網最詳細的AbstractQueuedSynchronizer(AQS)源碼剖析 系列

<code>getCount</code>方法就是傳回<code>CountDownLatch</code>對象目前的計數值,源碼如下:

實際上委托給了<code>sync</code>對象的<code>getCount</code>方法來執行,其源碼如下:

其實就是調用AQS的<code>getState</code>方法,傳回目前的<code>state</code>,即<code>CountDownLatch</code>的計數值,很簡單哦~

當然,在上述場景中,也可以使用<code>Thread</code>的對象方法<code>join</code>來實作這一點,在主線程中調用所有子線程的<code>join</code>方法,再執行結果收集和統計任務。上面例子如果使用<code>join</code>方法來實作,代碼如下:

但是,和<code>CountDownLatch</code>借助AQS不同,<code>join</code>方法的執行原理是:不停地檢查調用線程是否執行完畢。如果沒有,則讓目前線程wait。否則才會調用<code>notifyAll</code>将目前線程喚醒

從執行原理上就可以看出它們的差別主要在于兩點:

<code>join</code>方法沒有<code>CountDownLatch</code>靈活:使用<code>join</code>方法必須等待調用線程執行完畢,後面就不能再繼續執行了。而<code>CountDownLatch</code>的<code>countDown</code>方法可以放在調用線程的<code>run</code>方法中間,這樣調用線程不必執行結束,就能喚醒其他<code>await</code>的線程

調用<code>join</code>方法的線程會一直消耗CPU資源,不會阻塞挂起,即“忙等”,而調用了<code>CountDownLatch</code>的<code>await</code>方法的線程會被阻塞挂起,讓出CPU執行權,隻有等條件合适并被線程排程後才能占用CPU資源

願歸來仍是少年!

    作者:酒冽

    出處:https://www.cnblogs.com/frankiedyz/p/15730573.html

    版權:本文版權歸作者和部落格園共有

    轉載:歡迎轉載,但未經作者同意,必須保留此段聲明;必須在文章中給出原文連接配接;否則必究法律責任