CountDownLatch 核心定位
java.util.concurrent.CountDownLatch 是一個同步輔助類,允許一或多個執行緒等待其他執行緒完成操作。基於 AQS 共享鎖模式實作,提供「倒數鎖」語意:
- 建立時指定計數值 N
countDown() 減少計數
await() 阻塞直到計數歸零
- 計數歸零後不可重置(一次性使用)
核心 API
| 方法 | 說明 |
| CountDownLatch(int count) | 建構子,設定初始計數 |
| void await() | 阻塞直到計數歸零 |
| boolean await(long timeout, TimeUnit unit) | 超時等待,返回是否成功 |
| void countDown() | 計數減一,歸零喚醒所有等待者 |
| long getCount() | 取得當前計數值 |
經典應用場景一:主執行緒等待多個工作者完成
public class WorkerStartup {
public static void main(String[] args) throws InterruptedException {
int workerCount = 3;
CountDownLatch startLatch = new CountDownLatch(1); // 統一起跑信號
CountDownLatch doneLatch = new CountDownLatch(workerCount); // 完成等待
for (int i = 0; i < workerCount; i++) {
final int id = i;
new Thread(() -> {
try {
startLatch.await(); // 等待起跑信號
System.out.println("Worker " + id + " 開始工作");
Thread.sleep((long)(Math.random() * 2000));
System.out.println("Worker " + id + " 完成工作");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
doneLatch.countDown(); // 完成計數
}
}).start();
}
System.out.println("主執行緒發送起跑信號");
startLatch.countDown(); // 釋放所有工作者
System.out.println("主執行緒等待所有工作者完成...");
doneLatch.await(); // 阻塞直到計數歸零
System.out.println("所有工作者完成,主執行緒繼續");
}
}
場景二:多階段任務同步(分階段倒數)
public class MultiPhaseTask {
public static void main(String[] args) throws InterruptedException {
CountDownLatch phase1 = new CountDownLatch(3);
CountDownLatch phase2 = new CountDownLatch(2);
CountDownLatch phase3 = new CountDownLatch(1);
// 階段 1:3 個並行子任務
for (int i = 1; i <= 3; i++) {
final int taskId = i;
new Thread(() -> {
doWork("Phase1-Task" + taskId, 1000);
phase1.countDown();
}).start();
}
phase1.await();
System.out.println("=== 階段 1 完成,進入階段 2 ===");
// 階段 2:2 個並行子任務
for (int i = 1; i <= 2; i++) {
final int taskId = i;
new Thread(() -> {
doWork("Phase2-Task" + taskId, 1500);
phase2.countDown();
}).start();
}
phase2.await();
System.out.println("=== 階段 2 完成,進入階段 3 ===");
// 階段 3:單一收尾任務
new Thread(() -> {
doWork("Phase3-Final", 500);
phase3.countDown();
}).start();
phase3.await();
System.out.println("=== 所有階段完成 ===");
}
static void doWork(String name, int ms) {
try {
System.out.println(name + " 開始");
Thread.sleep(ms);
System.out.println(name + " 完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
場景三:服務啟動依賴檢查
public class ServiceStartup {
private final CountDownLatch dbReady = new CountDownLatch(1);
private final CountDownLatch cacheReady = new CountDownLatch(1);
private final CountDownLatch mqReady = new CountDownLatch(1);
public void startDatabase() {
new Thread(() -> {
simulateInit("Database", 2000);
dbReady.countDown();
}).start();
}
public void startCache() {
new Thread(() -> {
simulateInit("Cache", 1500);
cacheReady.countDown();
}).start();
}
public void startMessageQueue() {
new Thread(() -> {
simulateInit("MessageQueue", 3000);
mqReady.countDown();
}).start();
}
public void waitForAllServices() throws InterruptedException {
// 並行等待所有服務就緒
dbReady.await();
cacheReady.await();
mqReady.await();
System.out.println("所有基礎服務就緒,啟動業務應用");
}
private void simulateInit(String name, int ms) {
try {
System.out.println(name + " 初始化中...");
Thread.sleep(ms);
System.out.println(name + " 初始化完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
CountDownLatch vs CyclicBarrier vs CompletableFuture
| 特性 | CountDownLatch | CyclicBarrier | CompletableFuture |
| 可重用性 | 一次性 | 可重置 (reset) | 一次性 (但可組合) |
| 等待對象 | 計數歸零 | 所有執行緒到達屏障 | Future 完成 |
| 執行緒角色 | 主/工作者分離 | 對等參與者 | 任務導向 |
| 異常處理 | 無內建 | 可設 barrierAction | 完善異常傳播 |
| 適用場景 | 啟動等待、分階段同步 | 迭代計算、並行聚合 | 複雜非同步編排 |
進階技巧與注意事項
- 不可重置:需重複使用請改用
CyclicBarrier 或新建實例
- 計數不可增加:僅能
countDown(),建構後固定
- await() 可中斷:拋出
InterruptedException,需處理中斷狀態
- 超時等待:
await(5, TimeUnit.SECONDS) 返回 false 表示超時
- 記憶體可見性:
countDown() happens-before await() 返回
常見錯誤避坑
| 錯誤 | 後果 | 修正 |
| countDown() 次數超過初始計數 | 計數變負數,await 永不阻塞 | 確保邏輯正確,或用 AtomicInteger 輔助 |
| 在 await() 前未啟動工作者 | 主執行緒永久阻塞 | 先啟動所有執行緒再 await |
| 工作者異常未 countDown() | 主執行緒永久等待 | finally 區塊確保 countDown() |
| 重複使用同一實例 | 第二次 await 立即通過 | 每階段新建實例 |