點(diǎn)擊?Mr.綿羊的知識(shí)星球?解鎖更多優(yōu)質(zhì)文章。
目錄
一、介紹
二、特性
三、實(shí)現(xiàn)原理
四、適用場(chǎng)景
五、注意事項(xiàng)
六、實(shí)際應(yīng)用
一、介紹
? ? CountDownLatch?是 Java 中的一個(gè)并發(fā)工具類(lèi),用于協(xié)調(diào)多個(gè)線(xiàn)程之間的同步。其作用是讓某一個(gè)線(xiàn)程等待多個(gè)線(xiàn)程的操作完成之后再執(zhí)行。它可以使一個(gè)或多個(gè)線(xiàn)程等待一組事件的發(fā)生,而其他的線(xiàn)程則可以觸發(fā)這組事件。
二、特性
1. CountDownLatch?可以用于控制一個(gè)或多個(gè)線(xiàn)程等待多個(gè)任務(wù)完成后再執(zhí)行。
2. CountDownLatch?的計(jì)數(shù)器只能夠被減少,不能夠被增加。
3. CountDownLatch?的計(jì)數(shù)器初始值為正整數(shù),每次調(diào)用?countDown()?方法會(huì)將計(jì)數(shù)器減 1,計(jì)數(shù)器為 0 時(shí),等待線(xiàn)程開(kāi)始執(zhí)行。
三、實(shí)現(xiàn)原理
? ? CountDownLatch?的實(shí)現(xiàn)原理比較簡(jiǎn)單,它主要依賴(lài)于?AQS(AbstractQueuedSynchronizer)框架來(lái)實(shí)現(xiàn)線(xiàn)程的同步。
? ? CountDownLatch?內(nèi)部維護(hù)了一個(gè)計(jì)數(shù)器,該計(jì)數(shù)器初始值為?N,代表需要等待的線(xiàn)程數(shù)目,當(dāng)一個(gè)線(xiàn)程完成了需要等待的任務(wù)后,就會(huì)調(diào)用?countDown()?方法將計(jì)數(shù)器減 1,當(dāng)計(jì)數(shù)器的值為 0 時(shí),等待的線(xiàn)程就會(huì)開(kāi)始執(zhí)行。
四、適用場(chǎng)景
1. 主線(xiàn)程等待多個(gè)子線(xiàn)程完成任務(wù)后再繼續(xù)執(zhí)行。例如:一個(gè)大型的任務(wù)需要被拆分成多個(gè)子任務(wù)并交由多個(gè)線(xiàn)程并行處理,等所有子任務(wù)都完成后再將處理結(jié)果進(jìn)行合并。
2. 啟動(dòng)多個(gè)線(xiàn)程并發(fā)執(zhí)行任務(wù),等待所有線(xiàn)程執(zhí)行完畢后進(jìn)行結(jié)果匯總。例如:在一個(gè)并發(fā)請(qǐng)求量比較大的 Web 服務(wù)中,可以使用?CountDownLatch?控制多個(gè)線(xiàn)程同時(shí)處理請(qǐng)求,等待所有線(xiàn)程處理完畢后將結(jié)果進(jìn)行匯總。
3. 線(xiàn)程 A 等待線(xiàn)程 B 執(zhí)行完某個(gè)任務(wù)后再執(zhí)行自己的任務(wù)。例如:在分布式系統(tǒng)中,一個(gè)節(jié)點(diǎn)需要等待其他節(jié)點(diǎn)的加入后才能執(zhí)行某個(gè)任務(wù),可以使用?CountDownLatch?控制節(jié)點(diǎn)的加入,等所有節(jié)點(diǎn)都加入完成后再執(zhí)行任務(wù)。
4. 多個(gè)線(xiàn)程等待一個(gè)共享資源的初始化完成后再進(jìn)行操作。例如:在某個(gè)資源初始化較慢的系統(tǒng)中,可以使用?CountDownLatch?控制多個(gè)線(xiàn)程等待共享資源初始化完成后再進(jìn)行操作。
CountDownLatch?適用于多線(xiàn)程任務(wù)的協(xié)同處理場(chǎng)景,能夠有效提升多線(xiàn)程任務(wù)的執(zhí)行效率,同時(shí)也能夠降低多線(xiàn)程任務(wù)的復(fù)雜度和出錯(cuò)率。
五、注意事項(xiàng)
1. CountDownLatch?對(duì)象的計(jì)數(shù)器只能減不能增,即一旦計(jì)數(shù)器為 0,就無(wú)法再重新設(shè)置為其他值,因此在使用時(shí)需要根據(jù)實(shí)際需要設(shè)置初始值。
2. CountDownLatch?的計(jì)數(shù)器是線(xiàn)程安全的,多個(gè)線(xiàn)程可以同時(shí)調(diào)用?countDown()?方法,而不會(huì)產(chǎn)生沖突。
3. 如果?CountDownLatch?的計(jì)數(shù)器已經(jīng)為 0,再次調(diào)用?countDown()?方法也不會(huì)產(chǎn)生任何效果。
4. 如果在等待過(guò)程中,有線(xiàn)程發(fā)生異常或被中斷,計(jì)數(shù)器的值可能不會(huì)減少到 0,因此在使用時(shí)需要根據(jù)實(shí)際情況進(jìn)行異常處理。
5. CountDownLatch?可以與其他同步工具(如?Semaphore、CyclicBarrier)結(jié)合使用,實(shí)現(xiàn)更復(fù)雜的多線(xiàn)程同步。
六、實(shí)際應(yīng)用
1. 案例一
? ? (1) 場(chǎng)景
? ? 一個(gè)簡(jiǎn)單的?CountDownLatch?示例,演示了如何使用?CountDownLatch?實(shí)現(xiàn)多個(gè)線(xiàn)程的同步。
? ? (2) 代碼
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* CountDownLatchCase1
* 如何使用CountDownLatch實(shí)現(xiàn)多個(gè)線(xiàn)程的同步。
*
* @author wxy
* @since 2023-04-18
*/
public class CountDownLatchCase1 {
private static final Logger LOGGER = LoggerFactory.getLogger(CountDownLatchCase1.class);
public static void main(String[] args) throws InterruptedException {
// 創(chuàng)建 CountDownLatch 對(duì)象,需要等待 3 個(gè)線(xiàn)程完成任務(wù)
CountDownLatch latch = new CountDownLatch(3);
// 創(chuàng)建 3 個(gè)線(xiàn)程
Worker worker1 = new Worker(latch, "worker1");
Worker worker2 = new Worker(latch, "worker2");
Worker worker3 = new Worker(latch, "worker3");
// 啟動(dòng) 3 個(gè)線(xiàn)程
worker1.start();
worker2.start();
worker3.start();
// 等待 3 個(gè)線(xiàn)程完成任務(wù)
latch.await();
// 所有線(xiàn)程完成任務(wù)后,執(zhí)行下面的代碼
LOGGER.info("All workers have finished their jobs!");
}
}
class Worker extends Thread {
private static final Logger LOGGER = LoggerFactory.getLogger(Worker.class);
private final CountDownLatch latch;
public String name;
public Worker(CountDownLatch latch, String name) {
this.latch = latch;
this.name = name;
}
@Override
public void run() {
try {
// 模擬任務(wù)耗時(shí)
TimeUnit.MILLISECONDS.sleep(1000);
LOGGER.info("{} has finished the job!", name);
} catch (InterruptedException e) {
LOGGER.error(e.getMessage(), e);
} finally {
// 一定要保證每個(gè)線(xiàn)程執(zhí)行完畢或者異常后調(diào)用countDown()方法
// 如果不調(diào)用會(huì)導(dǎo)致其他線(xiàn)程一直等待, 無(wú)法繼續(xù)執(zhí)行
// 建議放在finally代碼塊中, 防止異常情況下未調(diào)用countDown()方法
latch.countDown();
}
}
}
運(yùn)行結(jié)果:
? ? 在上面的代碼中,首先創(chuàng)建了一個(gè)CountDownLatch對(duì)象,并指定需要等待的線(xiàn)程數(shù)為 3。然后創(chuàng)建了 3 個(gè)線(xiàn)程并啟動(dòng)。每個(gè)線(xiàn)程會(huì)模擬執(zhí)行一個(gè)耗時(shí)的任務(wù),執(zhí)行完成后會(huì)調(diào)用?countDown()?方法將計(jì)數(shù)器減 1。在所有線(xiàn)程都完成任務(wù)后,主線(xiàn)程會(huì)執(zhí)行?latch.await()?方法等待計(jì)數(shù)器為 0,然后輸出所有線(xiàn)程都完成任務(wù)的提示信息。
? ? 思考:如果不使用CountDownLatch情況將會(huì)是怎樣呢?
? ? 運(yùn)行結(jié)果:
? ? 由執(zhí)行結(jié)果可知,主線(xiàn)程不會(huì)等待子線(xiàn)程結(jié)束后再執(zhí)行。如果我們主線(xiàn)程(main) 需要其他線(xiàn)程執(zhí)行后的結(jié)果,我們就需要使用countDownLantch讓主線(xiàn)程和執(zhí)行快的線(xiàn)程等待子線(xiàn)程全部執(zhí)行完畢再向下執(zhí)行。
? ? 思考:如果某個(gè)線(xiàn)程漏調(diào)用.countDown();會(huì)怎么樣呢?
? ? 接下來(lái)我們模擬worker1線(xiàn)程異常,如果該線(xiàn)程異常latch.countDown()方法就無(wú)法被調(diào)用。
public void run() {
try {
// 模擬任務(wù)耗時(shí)
if ("worker1".equals(name)) {
throw new RuntimeException(name + "運(yùn)行異常");
}
TimeUnit.MILLISECONDS.sleep(1000);
LOGGER.info("{} has finished the job!", name);
latch.countDown();
} catch (InterruptedException e) {
LOGGER.error(e.getMessage(), e);
}
}
? ? 運(yùn)行結(jié)果:
? ? 由運(yùn)行結(jié)果可知,當(dāng)worker1線(xiàn)程由于異常沒(méi)有執(zhí)行countDown()方法,最后state結(jié)果不為0,導(dǎo)致所有線(xiàn)程停在AQS中自旋(死循環(huán))。所以程序無(wú)法結(jié)束。(如何解決這個(gè)問(wèn)題呢?請(qǐng)看案例二)
2. 案例二
? ? (1) 場(chǎng)景
? ? 當(dāng)年剛工作不久,遇到一個(gè)這樣的問(wèn)題:遠(yuǎn)程調(diào)用某個(gè)api,大部分情況下需要2-3s才能讀取到響應(yīng)值。我需要解析響應(yīng)的JSON用于后續(xù)的操作。由于這個(gè)調(diào)用是異步的,我沒(méi)辦法在主線(xiàn)程獲取到響應(yīng)的JSON值。
? ? 當(dāng)時(shí)第一時(shí)間想到的是讓主線(xiàn)程休眠,但是休眠多久好呢?1、2、3s?顯然是不行的,如果1s就請(qǐng)求成功并響應(yīng)了,你要等3s,這不是浪費(fèi)時(shí)間嗎!
? ? 于是,我就請(qǐng)教了公司一位大佬。他告訴我使用CountDownLatch。我恍然大悟,之前自己學(xué)過(guò),但是一到戰(zhàn)場(chǎng)上我就把他給忘記了(實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn))。
(2) 代碼(偷個(gè)懶 哈哈 就是在案例一的代碼中修改了await()方法)
? ? 將latch.await()修改為 latch.await(5, TimeUnit.SECONDS),這段代碼啥意思呢?就是當(dāng)?shù)谝粋€(gè)線(xiàn)程到達(dá)await()方法開(kāi)始計(jì)時(shí),5s后不等待未執(zhí)行完畢的線(xiàn)程,直接向下執(zhí)行。這么寫(xiě)的好處是,當(dāng)調(diào)用某個(gè)方法超時(shí)太久,不影響我們的主邏輯。(很實(shí)用)
// 等待 3 個(gè)線(xiàn)程完成任務(wù)
if (!latch.await(5, TimeUnit.SECONDS)) {
LOGGER.warn("{} time out", worker1.name);
}
// 所有線(xiàn)程完成任務(wù)后,執(zhí)行下面的代碼
LOGGER.info("all workers have finished their jobs!");
? ? 看一下加了latch.await(5, TimeUnit.SECONDS)方法后執(zhí)行結(jié)果:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-743696.html
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-743696.html
到了這里,關(guān)于CountDownLatch介紹和使用【Java多線(xiàn)程必備】的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!