JUC系列學(xué)習(xí)工具類CountDownLatch詳解
前言:
項(xiàng)目中我們經(jīng)常會(huì)遇到有時(shí)候需要等待其他線程完成任務(wù)后,主線程才能執(zhí)行其他任務(wù),那么我們將如何實(shí)現(xiàn)呢?
Join 解決方案
join 的工作原理是,檢查thread是否存活,如果存活則讓當(dāng)前線程永遠(yuǎn)wait,直到 thread線程終止,線程的 notifyAll才會(huì)被調(diào)用。
具體實(shí)現(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的實(shí)現(xiàn)我們需要每個(gè)線程進(jìn)行join,如果存在多個(gè)線程,那么寫起來會(huì)比較的繁瑣,那么又沒更新優(yōu)化的方案了,答案是JUC下面的工具類CountDownLatch,也能完成同樣的功能。
CountDownLatch 解決方案
具體實(shí)現(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 { //計(jì)數(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中有兩個(gè)方法一個(gè)是await()方法,調(diào)用這個(gè)方法的線程會(huì)被阻塞,另外一個(gè)是countDown() 方法,調(diào)用此方法會(huì)使計(jì)數(shù)器減一,當(dāng)計(jì)數(shù)器的值為0時(shí),調(diào)用await()方法被阻塞的線程才會(huì)被喚醒。
執(zhí)行結(jié)果:
原理說明
CountDownLatch 是一個(gè)計(jì)數(shù)器閉鎖,通過它可以完成類似于阻塞當(dāng)前線程的功能,即:一個(gè)線程或多個(gè)線程一直等待,直到其他線程執(zhí)行的操作完成。
基本原理
CountDownLatch
CountDownLatch內(nèi)部定義計(jì)數(shù)器和一個(gè)隊(duì)列。當(dāng)計(jì)數(shù)器的值遞減為0之前,阻塞隊(duì)列里面的線程處于掛起狀態(tài),當(dāng)計(jì)數(shù)器遞減到0時(shí)會(huì)喚醒阻塞隊(duì)列所有線程,計(jì)數(shù)器是一個(gè)標(biāo)志,可以表示一個(gè)任務(wù)一個(gè)線程,也可以表示一個(gè)倒計(jì)時(shí)器。
常用的方法
countDown:用于使計(jì)數(shù)器減一,其一般是執(zhí)行任務(wù)的線程調(diào)用. await: 使用線程處于等待狀態(tài),其一般是主線程調(diào)用.
countDown
countDown實(shí)現(xiàn)方法如下:
說明:sync是一個(gè)AQS的隊(duì)列,調(diào)用的為AQS的releaseShared方法,其具體實(shí)現(xiàn)如下:
而releaseShared調(diào)用為CountDownLatch中的內(nèi)部類sync中的tryReleaseShared方法,具體實(shí)現(xiàn)如下:
tryReleaseShared(int)方法即對state屬性進(jìn)行減一操作的代碼.通過CAS進(jìn)行減操作來保證原子性,其會(huì)比較state是否為c,如果是則將其設(shè)置為nextc(自減1),如果state不為c,則說明有另外的線程在getState()方法和compareAndSetState()方法調(diào)用之間對state進(jìn)行了設(shè)置,當(dāng)前線程也就沒有成功設(shè)置state屬性的值,其會(huì)進(jìn)入下一次循環(huán)中,如此往復(fù),直至其成功設(shè)置state屬性的值,即countDown()方法調(diào)用成功。
而doReleaseShared方法調(diào)用的為AbstractQueuedSynchronizer簡稱AQS的doReleaseShared方法,
說明:首先判斷頭結(jié)點(diǎn)不為空,且不為尾節(jié)點(diǎn),說明等待隊(duì)列中有等待喚醒的線程,這里需要說明的是,在等待隊(duì)列中,頭節(jié)點(diǎn)中并沒有保存正在等待的線程,其只是一個(gè)空的Node對象,真正等待的線程是從頭節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)開始存放的,因而會(huì)有對頭結(jié)點(diǎn)是否等于尾節(jié)點(diǎn)的判斷。在判斷等待隊(duì)列中有正在等待的線程之后,其會(huì)清除頭結(jié)點(diǎn)的狀態(tài)信息,并且調(diào)用unparkSuccessor(Node)方法喚醒頭結(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn),使其繼續(xù)往下執(zhí)行。如下是unparkSuccessor(Node)方法的具體實(shí)現(xiàn):
可以看到,unparkSuccessor(Node)方法的作用是喚醒離傳入節(jié)點(diǎn)最近的一個(gè)處于等待狀態(tài)的線程,使其繼續(xù)往下執(zhí)行。
await
await方法實(shí)現(xiàn)如下:
await()方法調(diào)用了Sync對象的方法acquireSharedInterruptibly(int)方法,該方法的具體實(shí)現(xiàn)如下:
在doAcquireSharedInterruptibly(int)方法中,首先使用當(dāng)前線程創(chuàng)建一個(gè)共享模式的節(jié)點(diǎn)。然后在一個(gè)for循環(huán)中判斷當(dāng)前線程是否獲取到執(zhí)行權(quán)限,如果有(r >= 0判斷)則將當(dāng)前節(jié)點(diǎn)設(shè)置為頭節(jié)點(diǎn),并且喚醒后續(xù)處于共享模式的節(jié)點(diǎn);如果沒有,則對調(diào)用shouldParkAfterFailedAcquire(Node, Node)和parkAndCheckInterrupt()方法使當(dāng)前線程處于"擱置"狀態(tài),該"擱置"狀態(tài)是由操作系統(tǒng)進(jìn)行的,這樣可以避免該線程無限循環(huán)而獲取不到執(zhí)行權(quán)限,造成資源浪費(fèi),這里也就是線程處于等待狀態(tài)的位置,也就是說當(dāng)線程被阻塞的時(shí)候就是阻塞在這個(gè)位置。當(dāng)有多個(gè)線程調(diào)用await()方法而進(jìn)入等待狀態(tài)時(shí),這幾個(gè)線程都將等待在此處。
總結(jié)
本文對JUC的工具類CountDownLatch進(jìn)行詳細(xì)的講解,如有疑問請隨時(shí)反饋。
到此這篇關(guān)于JUC系列學(xué)習(xí)工具類CountDownLatch詳解的文章就介紹到這了,更多相關(guān)JUC工具類CountDownLatch 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot雪花算法主鍵ID傳到前端后精度丟失問題的解決
本文主要介紹了SpringBoot雪花算法主鍵ID傳到前端后精度丟失問題的解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08SpringBoot框架實(shí)現(xiàn)切換啟動(dòng)開發(fā)環(huán)境和測試環(huán)境
這篇文章主要介紹了SpringBoot框架實(shí)現(xiàn)切換啟動(dòng)開發(fā)環(huán)境和測試環(huán)境,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12spring boot 防止重復(fù)提交實(shí)現(xiàn)方法詳解
這篇文章主要介紹了spring boot 防止重復(fù)提交實(shí)現(xiàn)方法,結(jié)合實(shí)例形式詳細(xì)分析了spring boot 防止重復(fù)提交具體配置、實(shí)現(xiàn)方法及操作注意事項(xiàng),需要的朋友可以參考下2019-11-11springboot3請求參數(shù)種類及接口測試案例小結(jié)
這篇文章主要介紹了springboot3請求參數(shù)種類及接口測試案例小結(jié),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-10-10攔截JSP頁面,校驗(yàn)是否已登錄詳解及實(shí)現(xiàn)代碼
這篇文章主要介紹了攔截JSP頁面,校驗(yàn)是否已登錄詳解及實(shí)現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2016-11-11Java的springcloud Sentinel是什么你知道嗎
這篇文章主要介紹了Java之springcloud Sentinel案例講解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08