Java并發(fā)工具類之CountDownLatch詳解
CountDownLatch
1.概述
CountDownLatch可以使一個(gè)獲多個(gè)線程等待其他線程各自執(zhí)行完畢后再執(zhí)行。
CountDownLatch 定義了一個(gè)計(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í)器,CountDownLatch可以解決那些一個(gè)或者多個(gè)線程在執(zhí)行之前必須依賴于某些必要的前提業(yè)務(wù)先執(zhí)行的場(chǎng)景。
2.常用方法
CountDownLatch(int count); //構(gòu)造方法,創(chuàng)建一個(gè)值為count 的計(jì)數(shù)器。 ? await();//阻塞當(dāng)前線程,將當(dāng)前線程加入阻塞隊(duì)列。 ? await(long timeout, TimeUnit unit);//在timeout的時(shí)間之內(nèi)阻塞當(dāng)前線程,時(shí)間一過(guò)則當(dāng)前線程可以執(zhí)行, ? countDown();//對(duì)計(jì)數(shù)器進(jìn)行遞減1操作,當(dāng)計(jì)數(shù)器遞減至0時(shí),當(dāng)前線程會(huì)去喚醒阻塞隊(duì)列里的所有線程。
3.應(yīng)用
我們經(jīng)常會(huì)在一個(gè)接口中調(diào)用多個(gè)第三方接口,然后將結(jié)果返回,其實(shí)就可以通過(guò)CountDownLatch來(lái)實(shí)現(xiàn)
public static void main(String[] args) { CountDownLatch count = new CountDownLatch(3); Thread thread = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep((int)(Math.random()*1000)); System.out.println("獲取接口一的數(shù)據(jù)"); count.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep((int)(Math.random()*1000)); System.out.println("獲取接口二的數(shù)據(jù)"); count.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread thread3 = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep((int)(Math.random()*1000)); System.out.println("獲取接口三的數(shù)據(jù)"); count.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); thread2.start(); thread3.start(); try { count.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("執(zhí)行成功"); }
4.實(shí)現(xiàn)原理
(1)創(chuàng)建計(jì)數(shù)器
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count);//創(chuàng)建同步隊(duì)列,并設(shè)置初始計(jì)數(shù)器值 }
(2)Sync類
可以看出該類是繼承AQS的,所以CountDownLatch的實(shí)現(xiàn)大多都是通過(guò)AQS來(lái)實(shí)現(xiàn)
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; } } }
(3)await方法
當(dāng)我們調(diào)用countDownLatch.wait()的時(shí)候,會(huì)創(chuàng)建一個(gè)節(jié)點(diǎn),加入到AQS阻塞隊(duì)列,并同時(shí)把當(dāng)前線程掛起,其實(shí)就是調(diào)用共享模式下的鎖獲取,詳情看AQS文章
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
在Sync類重寫的tryAcquireShared()方法中g(shù)etState()只有等于0才會(huì)獲取到鎖,所以當(dāng)countDownLatch待執(zhí)行的任務(wù)數(shù)大于0都會(huì)堵塞該線程直到所有任務(wù)都完成
protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; }
AQS中觸發(fā)堵塞線程的源碼:
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException { //新建節(jié)點(diǎn)加入阻塞隊(duì)列 final Node node = addWaiter(Node.SHARED); boolean failed = true; try { for (;;) { //獲得當(dāng)前節(jié)點(diǎn)pre節(jié)點(diǎn) final Node p = node.predecessor(); if (p == head) { int r = tryAcquireShared(arg);//返回鎖的state if (r >= 0) { setHeadAndPropagate(node, r); p.next = null; // help GC failed = false; return; } } //重組雙向鏈表,清空無(wú)效節(jié)點(diǎn),掛起當(dāng)前線程 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
(4)countDown()方法
當(dāng)我們調(diào)用countDownLatch.countDown()方法的時(shí)候,會(huì)對(duì)計(jì)數(shù)器進(jìn)行減1操作,AQS內(nèi)部是通過(guò)釋放鎖的方式,對(duì)state進(jìn)行減1操作,當(dāng)state=0的時(shí)候證明計(jì)數(shù)器已經(jīng)遞減完畢,此時(shí)會(huì)將AQS阻塞隊(duì)列里的節(jié)點(diǎn)線程全部喚醒。
public void countDown() { //遞減鎖重入次數(shù),當(dāng)state=0時(shí)喚醒所有阻塞線程 sync.releaseShared(1); }
public final boolean releaseShared(int arg) { //遞減鎖的重入次數(shù) if (tryReleaseShared(arg)) { doReleaseShared();//喚醒隊(duì)列所有阻塞的節(jié)點(diǎn) return true; } return false; } private void doReleaseShared() { //喚醒所有阻塞隊(duì)列里面的線程 for (;;) { Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; if (ws == Node.SIGNAL) {//節(jié)點(diǎn)是否在等待喚醒狀態(tài) if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//修改狀態(tài)為初始 continue; unparkSuccessor(h);//成功則喚醒線程 } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } if (h == head) // loop if head changed break; } }
到此這篇關(guān)于Java并發(fā)工具類之CountDownLatch詳解的文章就介紹到這了,更多相關(guān)CountDownLatch詳解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java實(shí)現(xiàn)文件打包壓縮輸出到瀏覽器下載
這篇文章主要介紹了java實(shí)現(xiàn)文件打包壓縮輸出到瀏覽器下載,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11SpringBoot整合SpringSecurity和JWT和Redis實(shí)現(xiàn)統(tǒng)一鑒權(quán)認(rèn)證
Spring Security是一個(gè)可以為Java應(yīng)用程序提供全面安全服務(wù)的框架,同時(shí)它也可以輕松擴(kuò)展以滿足自定義需求,本文主要介紹了SpringBoot整合SpringSecurity和JWT和Redis實(shí)現(xiàn)統(tǒng)一鑒權(quán)認(rèn)證,感興趣的可以了解一下2023-11-11HashMap原理及put方法與get方法的調(diào)用過(guò)程
這篇文章主要介紹了HashMap原理及put方法與get方法的調(diào)用過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09MultipartFile中transferTo(File file)的路徑問題及解決
這篇文章主要介紹了MultipartFile中transferTo(File file)的路徑問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Java中如何使用?byte?數(shù)組作為?Map?的?key
本文將討論在使用HashMap時(shí),當(dāng)byte數(shù)組作為key時(shí)所遇到的問題及其解決方案,介紹使用String和List這兩種數(shù)據(jù)結(jié)構(gòu)作為臨時(shí)解決方案的方法,感興趣的朋友跟隨小編一起看看吧2023-06-06java實(shí)現(xiàn)聯(lián)機(jī)五子棋
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)聯(lián)機(jī)五子棋,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05