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類重寫(xiě)的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-11
SpringBoot整合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-11
HashMap原理及put方法與get方法的調(diào)用過(guò)程
這篇文章主要介紹了HashMap原理及put方法與get方法的調(diào)用過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
MultipartFile中transferTo(File file)的路徑問(wèn)題及解決
這篇文章主要介紹了MultipartFile中transferTo(File file)的路徑問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
Java中如何使用?byte?數(shù)組作為?Map?的?key
本文將討論在使用HashMap時(shí),當(dāng)byte數(shù)組作為key時(shí)所遇到的問(wèn)題及其解決方案,介紹使用String和List這兩種數(shù)據(jù)結(jié)構(gòu)作為臨時(shí)解決方案的方法,感興趣的朋友跟隨小編一起看看吧2023-06-06
java實(shí)現(xiàn)聯(lián)機(jī)五子棋
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)聯(lián)機(jī)五子棋,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05

