JUC系列學(xué)習(xí)工具類CountDownLatch詳解
前言:
項目中我們經(jīng)常會遇到有時候需要等待其他線程完成任務(wù)后,主線程才能執(zhí)行其他任務(wù),那么我們將如何實現(xiàn)呢?
Join 解決方案
join 的工作原理是,檢查thread是否存活,如果存活則讓當(dāng)前線程永遠(yuǎn)wait,直到 thread線程終止,線程的 notifyAll才會被調(diào)用。
具體實現(xiàn)
public class JoinAThread extends Thread
{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +
" 線程開始");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( Thread.currentThread().getName() +
" 線程執(zhí)行完畢");
}
}
public class JoinBThread extends Thread
{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +
" 線程開始");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( Thread.currentThread().getName() +
" 線程執(zhí)行完畢");
}
}
public class JoinTest
{
public static void main(String[] args) throws InterruptedException
{
JoinAThread joinA =new JoinAThread();
Thread threadA =new Thread(joinA,"線程A");
JoinBThread joinB =new JoinBThread();
Thread threadB =new Thread(joinB,"線程B");
threadA.start();
threadB.start();
threadA.join();
threadB.join();
System.out.println("子線程執(zhí)行完成了,主線程"+Thread.currentThread().getName()+"開始執(zhí)行了");
}
}執(zhí)行結(jié)果

從結(jié)果中,我們可以看出只有子線程執(zhí)行完成了,主線程才開始執(zhí)行。join的實現(xiàn)我們需要每個線程進(jìn)行join,如果存在多個線程,那么寫起來會比較的繁瑣,那么又沒更新優(yōu)化的方案了,答案是JUC下面的工具類CountDownLatch,也能完成同樣的功能。
CountDownLatch 解決方案
具體實現(xiàn)
public class CountDownLatchTest
{
private static Logger logger =LoggerFactory.getLogger(CountDownLatchTest.class);
public static void main(String[] args) throws InterruptedException
{
ExecutorService exec = Executors.newCachedThreadPool();
final CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 1; i <= 10; i++){
exec.execute(() -> {
try {
invokeServiec();
} catch (InterruptedException e)
{
logger.info("invoce service error",e);
}
finally
{
//計數(shù)器減一
countDownLatch.countDown();
}
});
}
countDownLatch.await();
logger.info("所有的子線程執(zhí)行完成,主線程"+Thread.currentThread().getName()+"開始執(zhí)行");
}
private static void invokeServiec() throws InterruptedException
{
logger.info(Thread.currentThread().getName()+",開始執(zhí)行任務(wù)");
Thread.sleep(300);
}
}說明:CountDownLatch中有兩個方法一個是await()方法,調(diào)用這個方法的線程會被阻塞,另外一個是countDown() 方法,調(diào)用此方法會使計數(shù)器減一,當(dāng)計數(shù)器的值為0時,調(diào)用await()方法被阻塞的線程才會被喚醒。
執(zhí)行結(jié)果:

原理說明
CountDownLatch 是一個計數(shù)器閉鎖,通過它可以完成類似于阻塞當(dāng)前線程的功能,即:一個線程或多個線程一直等待,直到其他線程執(zhí)行的操作完成。
基本原理
CountDownLatch
CountDownLatch內(nèi)部定義計數(shù)器和一個隊列。當(dāng)計數(shù)器的值遞減為0之前,阻塞隊列里面的線程處于掛起狀態(tài),當(dāng)計數(shù)器遞減到0時會喚醒阻塞隊列所有線程,計數(shù)器是一個標(biāo)志,可以表示一個任務(wù)一個線程,也可以表示一個倒計時器。

常用的方法

countDown:用于使計數(shù)器減一,其一般是執(zhí)行任務(wù)的線程調(diào)用. await: 使用線程處于等待狀態(tài),其一般是主線程調(diào)用.
countDown
countDown實現(xiàn)方法如下:

說明:sync是一個AQS的隊列,調(diào)用的為AQS的releaseShared方法,其具體實現(xiàn)如下:

而releaseShared調(diào)用為CountDownLatch中的內(nèi)部類sync中的tryReleaseShared方法,具體實現(xiàn)如下:

tryReleaseShared(int)方法即對state屬性進(jìn)行減一操作的代碼.通過CAS進(jìn)行減操作來保證原子性,其會比較state是否為c,如果是則將其設(shè)置為nextc(自減1),如果state不為c,則說明有另外的線程在getState()方法和compareAndSetState()方法調(diào)用之間對state進(jìn)行了設(shè)置,當(dāng)前線程也就沒有成功設(shè)置state屬性的值,其會進(jìn)入下一次循環(huán)中,如此往復(fù),直至其成功設(shè)置state屬性的值,即countDown()方法調(diào)用成功。
而doReleaseShared方法調(diào)用的為AbstractQueuedSynchronizer簡稱AQS的doReleaseShared方法,

說明:首先判斷頭結(jié)點不為空,且不為尾節(jié)點,說明等待隊列中有等待喚醒的線程,這里需要說明的是,在等待隊列中,頭節(jié)點中并沒有保存正在等待的線程,其只是一個空的Node對象,真正等待的線程是從頭節(jié)點的下一個節(jié)點開始存放的,因而會有對頭結(jié)點是否等于尾節(jié)點的判斷。在判斷等待隊列中有正在等待的線程之后,其會清除頭結(jié)點的狀態(tài)信息,并且調(diào)用unparkSuccessor(Node)方法喚醒頭結(jié)點的下一個節(jié)點,使其繼續(xù)往下執(zhí)行。如下是unparkSuccessor(Node)方法的具體實現(xiàn):

可以看到,unparkSuccessor(Node)方法的作用是喚醒離傳入節(jié)點最近的一個處于等待狀態(tài)的線程,使其繼續(xù)往下執(zhí)行。
await
await方法實現(xiàn)如下:

await()方法調(diào)用了Sync對象的方法acquireSharedInterruptibly(int)方法,該方法的具體實現(xiàn)如下:


在doAcquireSharedInterruptibly(int)方法中,首先使用當(dāng)前線程創(chuàng)建一個共享模式的節(jié)點。然后在一個for循環(huán)中判斷當(dāng)前線程是否獲取到執(zhí)行權(quán)限,如果有(r >= 0判斷)則將當(dāng)前節(jié)點設(shè)置為頭節(jié)點,并且喚醒后續(xù)處于共享模式的節(jié)點;如果沒有,則對調(diào)用shouldParkAfterFailedAcquire(Node, Node)和parkAndCheckInterrupt()方法使當(dāng)前線程處于"擱置"狀態(tài),該"擱置"狀態(tài)是由操作系統(tǒng)進(jìn)行的,這樣可以避免該線程無限循環(huán)而獲取不到執(zhí)行權(quán)限,造成資源浪費,這里也就是線程處于等待狀態(tài)的位置,也就是說當(dāng)線程被阻塞的時候就是阻塞在這個位置。當(dāng)有多個線程調(diào)用await()方法而進(jìn)入等待狀態(tài)時,這幾個線程都將等待在此處。
總結(jié)
本文對JUC的工具類CountDownLatch進(jìn)行詳細(xì)的講解,如有疑問請隨時反饋。
到此這篇關(guān)于JUC系列學(xué)習(xí)工具類CountDownLatch詳解的文章就介紹到這了,更多相關(guān)JUC工具類CountDownLatch 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot-2.3.x最新版源碼閱讀環(huán)境搭建(基于gradle構(gòu)建)
這篇文章主要介紹了springboot-2.3.x最新版源碼閱讀環(huán)境搭建(基于gradle構(gòu)建),需要的朋友可以參考下2020-08-08
Java線程休眠_(dá)動力節(jié)點Java學(xué)院整理
sleep() 的作用是讓當(dāng)前線程休眠,即當(dāng)前線程會從“運(yùn)行狀態(tài)”進(jìn)入到“休眠(阻塞)狀態(tài)”。下面通過實例代碼給大家介紹Java線程休眠的知識,需要的朋友參考下吧2017-05-05
在SpringMVC框架下實現(xiàn)文件的上傳和下載示例
本篇文章主要介紹了在SpringMVC框架下實現(xiàn)文件的上傳和下載示例,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-02-02
java 從int數(shù)組中獲取最大數(shù)的方法
這篇文章主要介紹了java 從int數(shù)組中獲取最大數(shù)的方法,需要的朋友可以參考下2017-02-02
Java使用Apache Commons高效處理CSV文件的操作指南
在 Java 開發(fā)中,CSV(Comma-Separated Values,逗號分隔值)是一種常見的數(shù)據(jù)存儲格式,廣泛用于數(shù)據(jù)交換和簡單的存儲任務(wù),本文將介紹Java使用Apache Commons高效處理CSV文件的操作指南,需要的朋友可以參考下2025-03-03

