Java中的CountDownLatch多方面深入解析
前言
CountDownLatch 是 Java 并發(fā)包(java.util.concurrent)中的核心同步工具,用于協(xié)調(diào)多個(gè)線程的執(zhí)行順序,允許一個(gè)或多個(gè)線程等待其他線程組完成操作后再繼續(xù)執(zhí)行。其設(shè)計(jì)簡(jiǎn)潔高效,在企業(yè)級(jí)高并發(fā)場(chǎng)景中廣泛應(yīng)用。以下從核心特性、實(shí)現(xiàn)原理、企業(yè)實(shí)例三方面深入解析:
一、核心概念與特性??
??計(jì)數(shù)器機(jī)制??
- ??初始化??:創(chuàng)建時(shí)指定正整數(shù)
count(如new CountDownLatch(5)),表示需要等待的事件數(shù)量。 - ??遞減操作??:線程完成任務(wù)后調(diào)用
countDown(),計(jì)數(shù)器原子性減 1。 - ??等待機(jī)制??:調(diào)用
await()的線程阻塞,直到計(jì)數(shù)器歸零后自動(dòng)喚醒。
- ??初始化??:創(chuàng)建時(shí)指定正整數(shù)
??關(guān)鍵方法??
await():阻塞當(dāng)前線程直至計(jì)數(shù)器歸零(支持中斷)。await(long timeout, TimeUnit unit):支持超時(shí)等待,避免無(wú)限阻塞。countDown():計(jì)數(shù)器減 1,歸零時(shí)喚醒所有等待線程。getCount():獲取當(dāng)前計(jì)數(shù)器值(調(diào)試或監(jiān)控用)。
??核心特性??
- ??一次性??:計(jì)數(shù)器歸零后無(wú)法重置,若需復(fù)用需改用
CyclicBarrier。 - ??無(wú)鎖設(shè)計(jì)??:基于 AQS 實(shí)現(xiàn),
countDown()通過(guò) CAS 操作避免鎖競(jìng)爭(zhēng)。
- ??一次性??:計(jì)數(shù)器歸零后無(wú)法重置,若需復(fù)用需改用
二、底層實(shí)現(xiàn)原理??
??基于 AQS(AbstractQueuedSynchronizer)的共享鎖模式??:
??Sync 內(nèi)部類(lèi)??:
- 繼承 AQS,重寫(xiě)
tryAcquireShared和tryReleaseShared方法。 - ??
tryAcquireShared??:計(jì)數(shù)器為 0 時(shí)返回 1(允許線程通過(guò)),否則返回 -1(阻塞線程)。 - ??
tryReleaseShared??:protected boolean tryReleaseShared(int releases) { for (;;) { // CAS 自旋減計(jì)數(shù) int c = getState(); if (c == 0) return false; int nextc = c-1; if (compareAndSetState(c, nextc)) return nextc == 0; // 返回 true 時(shí)喚醒阻塞線程 } } ```[7](@ref)
- 繼承 AQS,重寫(xiě)
??線程阻塞與喚醒??:
- ??
await()?? → 調(diào)用acquireSharedInterruptibly(1),線程加入 AQS 阻塞隊(duì)列。 - ??
countDown()?? → 計(jì)數(shù)器歸零時(shí),AQS 釋放所有阻塞線程(通過(guò)doReleaseShared())。
- ??
??性能優(yōu)勢(shì)??:
countDown()無(wú)鎖操作(CAS),高并發(fā)下效率高;- 線程喚醒由 AQS 統(tǒng)一調(diào)度,避免“驚群效應(yīng)”。
三、企業(yè)級(jí)應(yīng)用場(chǎng)景與實(shí)例??
??場(chǎng)景 1:微服務(wù)啟動(dòng)協(xié)調(diào)??
??問(wèn)題??:系統(tǒng)需依賴(lài)多個(gè)服務(wù)(數(shù)據(jù)庫(kù)、緩存、消息隊(duì)列)啟動(dòng)完成后,主服務(wù)才能對(duì)外提供服務(wù)。
??解決方案??:
public class ServiceBootstrap {
private static final int SERVICE_COUNT = 3;
private static CountDownLatch latch = new CountDownLatch(SERVICE_COUNT);
public static void main(String[] args) {
// 啟動(dòng)依賴(lài)服務(wù)
startService("Database", 2000);
startService("Redis", 1500);
startService("MQ", 1000);
try {
latch.await(); // 阻塞主線程,等待所有服務(wù)就緒
System.out.println("所有服務(wù)啟動(dòng)完成,主服務(wù)開(kāi)始運(yùn)行!");
} catch (InterruptedException e) { /* ... */ }
}
private static void startService(String name, long delay) {
new Thread(() -> {
try {
Thread.sleep(delay); // 模擬服務(wù)初始化
System.out.println(name + " 服務(wù)就緒");
} catch (Exception e) { /* ... */ } finally {
latch.countDown(); // 服務(wù)就緒后減計(jì)數(shù)
}
}).start();
}
}??輸出??:
Redis 服務(wù)就緒
MQ 服務(wù)就緒
Database 服務(wù)就緒
所有服務(wù)啟動(dòng)完成,主服務(wù)開(kāi)始運(yùn)行!
```[4,7](@ref)
---
#### ?? **場(chǎng)景 2:電商詳情頁(yè)數(shù)據(jù)聚合**
**問(wèn)題**:商品詳情頁(yè)需并行調(diào)用交易服務(wù)(價(jià)格)、庫(kù)存服務(wù)(庫(kù)存)、推薦服務(wù)(相關(guān)商品),全部返回后才能渲染頁(yè)面。
**解決方案**:
```java
public class ProductDetailService {
private CountDownLatch latch = new CountDownLatch(3);
private PriceResult priceResult;
private StockResult stockResult;
private RecommendResult recommendResult;
public void loadDetail() throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(() -> {
priceResult = fetchPrice(); // 調(diào)用價(jià)格服務(wù)
latch.countDown();
});
executor.submit(() -> {
stockResult = fetchStock(); // 調(diào)用庫(kù)存服務(wù)
latch.countDown();
});
executor.submit(() -> {
recommendResult = fetchRecommends(); // 調(diào)用推薦服務(wù)
latch.countDown();
});
latch.await(500, TimeUnit.MILLISECONDS); // 超時(shí) 500ms 避免頁(yè)面卡死
renderPage(priceResult, stockResult, recommendResult);
}
}??優(yōu)勢(shì)??:
- 并行請(qǐng)求減少響應(yīng)時(shí)間(從串行 300ms → 并行 150ms);
- 超時(shí)機(jī)制保障服務(wù)降級(jí)能力。
場(chǎng)景 3:分布式任務(wù)分治??
??問(wèn)題??:日志分析系統(tǒng)需將 10GB 日志拆分為 100 個(gè)子任務(wù)并行處理,全部完成后匯總結(jié)果。
??解決方案??:
public class LogAnalyzer {
private static final int TASK_COUNT = 100;
private CountDownLatch latch = new CountDownLatch(TASK_COUNT);
private ResultAggregator aggregator = new ResultAggregator();
public void analyze() {
for (int i = 0; i < TASK_COUNT; i++) {
Thread task = new Thread(() -> {
TaskResult result = processSubTask(); // 處理子任務(wù)
aggregator.collect(result);
latch.countDown();
});
task.start();
}
latch.await(); // 阻塞主線程直至所有任務(wù)完成
aggregator.summarize(); // 生成最終報(bào)告
}
}??適用場(chǎng)景??:
- 大數(shù)據(jù)分片處理(如 MapReduce 中的 Map 階段同步);
- 批量訂單并發(fā)處理后的庫(kù)存結(jié)算。
??四、注意事項(xiàng)與替代方案??
??不可重用性??:
計(jì)數(shù)器歸零后無(wú)法重置,需根據(jù)場(chǎng)景選擇:- ??單次等待??:
CountDownLatch(如服務(wù)啟動(dòng)); - ??循環(huán)同步??:
CyclicBarrier(如分階段計(jì)算)。
- ??單次等待??:
??超時(shí)控制??:
務(wù)必使用await(long timeout, TimeUnit unit),避免線程永久阻塞導(dǎo)致系統(tǒng)僵死。??異常處理??:
- 子線程異常時(shí)需在
finally塊調(diào)用countDown(),防止主線程無(wú)限等待; - 中斷響應(yīng):
await()被中斷后拋出InterruptedException,需重置中斷狀態(tài)。
- 子線程異常時(shí)需在
??性能監(jiān)控??:
通過(guò)getCount()實(shí)時(shí)監(jiān)控未完成任務(wù)數(shù),結(jié)合日志定位瓶頸。
總結(jié)??
CountDownLatch 以 ??“計(jì)數(shù)器歸零”?? 為核心模型,通過(guò) AQS 的共享鎖機(jī)制實(shí)現(xiàn)高效線程協(xié)調(diào),適用于三類(lèi)企業(yè)場(chǎng)景:
- ??服務(wù)啟動(dòng)依賴(lài)??(等待多個(gè)組件初始化完成);
- ??并行任務(wù)聚合??(如數(shù)據(jù)分片處理、微服務(wù) API 組合);
- ??分布式任務(wù)同步??(批量任務(wù)完成后觸發(fā)匯總操作)。
其設(shè)計(jì)精髓在于 ??以無(wú)鎖 CAS 實(shí)現(xiàn)高并發(fā)計(jì)數(shù)??,但需警惕不可重用性與超時(shí)風(fēng)險(xiǎn)。對(duì)于復(fù)雜多階段協(xié)作,可結(jié)合 CompletableFuture 或 CyclicBarrier 擴(kuò)展功能。
到此這篇關(guān)于Java中CountDownLatch的文章就介紹到這了,更多相關(guān)Java中CountDownLatch內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺析SpringBoot2.4 靜態(tài)資源加載問(wèn)題
這篇文章主要介紹了SpringBoot2.4 靜態(tài)資源加載問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
Java中 URL實(shí)現(xiàn)斷點(diǎn)下載
Java中 URL實(shí)現(xiàn)斷點(diǎn)下載,需要的朋友可以參考一下2013-03-03
java控制臺(tái)實(shí)現(xiàn)學(xué)生管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java控制臺(tái)實(shí)現(xiàn)簡(jiǎn)單的學(xué)生管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02
SpringBoot整合騰訊云COS對(duì)象存儲(chǔ)實(shí)現(xiàn)文件上傳的示例代碼
本文主要介紹了SpringBoot整合騰訊云COS對(duì)象存儲(chǔ)實(shí)現(xiàn)文件上傳的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12
Mybatis批量更新數(shù)據(jù)庫(kù)錯(cuò)誤問(wèn)題
這篇文章主要介紹了Mybatis批量更新數(shù)據(jù)庫(kù)錯(cuò)誤問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
詳解Java8的groupBy實(shí)現(xiàn)集合的分組
這篇文章主要介紹了詳解Java8的groupBy實(shí)現(xiàn)集合的分組,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
如何在SpringBoot中添加攔截器忽略請(qǐng)求URL當(dāng)中的指定字符串
這篇文章主要介紹了在SpringBoot中添加攔截器忽略請(qǐng)求URL當(dāng)中的指定字符串,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08
SpringBoot應(yīng)用程序轉(zhuǎn)換成WAR文件詳解
其實(shí)一般使用SpringBoot使用打成jar包比較省事的,但也有很多童鞋是習(xí)慣使用WAR包的,下面這篇文章主要給大家介紹了關(guān)于SpringBoot轉(zhuǎn)換WAR的相關(guān)資料,需要的朋友可以參考下2022-11-11

