CountDownLatch 是 Java 5 引入的同步工具,以計數器機制實現「一個或多個執行緒等待其他執行緒完成操作」的協調模式。本文詳細說明核心 API、典型應用場景(服務啟動、並行計算、測試協調)、進階用法與常見陷阱。
public class CountDownLatch {
// 建立計數器為 count 的閂鎖
public CountDownLatch(int count) { ... }
// 計數器減 1,歸零時喚醒所有 await() 執行緒
public void countDown() { ... }
// 等待計數器歸零(阻塞)
public void await() throws InterruptedException { ... }
// 等待計數器歸零,支援超時
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { ... }
// 取得目前計數
public long getCount() { ... }
}
主執行緒等待多個子系統(資料庫連線池、快取預熱、配置中心拉取)並行初始化完成:
public class ServerStartup {
private static final Logger log = LoggerFactory.getLogger(ServerStartup.class);
public void start() throws InterruptedException {
int serviceCount = 3;
CountDownLatch latch = new CountDownLatch(serviceCount);
ExecutorService executor = Executors.newFixedThreadPool(serviceCount);
// 服務 1:資料庫連線池
executor.submit(() -> {
try {
initDatabasePool();
log.info("資料庫連線池初始化完成");
} finally {
latch.countDown();
}
});
// 服務 2:Redis 快取預熱
executor.submit(() -> {
try {
warmupCache();
log.info("快取預熱完成");
} finally {
latch.countDown();
}
});
// 服務 3:配置中心拉取
executor.submit(() -> {
try {
fetchRemoteConfig();
log.info("遠端配置拉取完成");
} finally {
latch.countDown();
}
});
// 主執行緒等待所有服務就緒,設定 30 秒超時防死鎖
boolean ready = latch.await(30, TimeUnit.SECONDS);
if (!ready) {
throw new IllegalStateException("服務啟動超時,部分服務未就緒");
}
log.info("所有核心服務就緒,伺服器正式啟動");
executor.shutdown();
}
// 模擬初始化方法...
}
將大任務拆分為多個子任務並行執行,主執行緒等待所有結果:
public class ParallelAggregator {
public String processLargeDataset(List data, int partitions) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(partitions);
List results = Collections.synchronizedList(new ArrayList<>());
ExecutorService executor = Executors.newFixedThreadPool(partitions);
int chunkSize = (data.size() + partitions - 1) / partitions;
for (int i = 0; i < partitions; i++) {
int start = i * chunkSize;
int end = Math.min(start + chunkSize, data.size());
List subList = data.subList(start, end);
executor.submit(() -> {
try {
String partial = processPartition(subList);
results.add(partial);
} finally {
latch.countDown();
}
});
}
latch.await(); // 等待所有分區處理完成
executor.shutdown();
return String.join("", results); // 合併結果
}
}
確保測試中多個非同步操作完成後再斷言:
@Test
void testAsyncEventProcessing() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
List events = Collections.synchronizedList(new ArrayList<>());
// 註冊三個事件監聽器
eventBus.subscribe("order.created", e -> { events.add(e); latch.countDown(); });
eventBus.subscribe("payment.completed", e -> { events.add(e); latch.countDown(); });
eventBus.subscribe("inventory.reserved", e -> { events.add(e); latch.countDown(); });
// 觸發業務流程
orderService.createOrder(new OrderRequest(...));
// 等待所有事件被處理,最多 5 秒
assertTrue(latch.await(5, TimeUnit.SECONDS), "事件處理超時");
assertEquals(3, events.size());
}
注意:CountDownLatch 計數器不可重置,歸零後無法重用。需重複使用請改用:
CyclicBarrier:固定參與方數量,可重用,支援屏障動作Phaser:Java 7+,動態註冊/取消參與方,階段式同步CompletableFuture.allOf():函數式風格,組合多個 Future| 陷阱 | 後果 | 解決方案 |
|---|---|---|
| countDown() 未在 finally 區塊 | 任務拋出異常導致計數器卡住,主執行緒永久阻塞 | 務必在 finally 或 try-with-resources 中呼叫 countDown() |
| await() 無超時 | 死鎖風險,應用掛起無法恢復 | 生產環境永遠使用 await(timeout, unit) |
| 重複使用同一實例 | 計數器歸零後 countDown() 無效果,await() 立即返回 | 每次協調建立新實例,或改用 CyclicBarrier/Phaser |
| 計數器初始值錯誤 | 過大 → 永不歸零;過小 → 提早釋放 | 初始值 = 實際需等待的任務數,編譯期常數或配置注入 |
| 大量執行緒同步等待 | 喚醒風暴,CPU 飆升 | 大規模協調考慮 Phaser 或分層閂鎖 |
import java.util.concurrent.*;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
int workerCount = 5;
CountDownLatch startLatch = new CountDownLatch(1); // 起跑槍
CountDownLatch endLatch = new CountDownLatch(workerCount); // 終點線
ExecutorService executor = Executors.newFixedThreadPool(workerCount);
for (int i = 1; i <= workerCount; i++) {
final int id = i;
executor.submit(() -> {
try {
startLatch.await(); // 等待起跑信號
Thread.sleep((long) (Math.random() * 2000)); // 模擬工作
System.out.println("Worker " + id + " 完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
endLatch.countDown();
}
});
}
System.out.println("準備起跑...");
startLatch.countDown(); // 發令槍
boolean finished = endLatch.await(10, TimeUnit.SECONDS);
System.out.println(finished ? "所有工人完成工作" : "超時,部分工人未完成");
executor.shutdown();
}
}
CountDownLatch 是一次性、不可重置的同步閂鎖,適用於「主執行緒等待多個工作者完成」的單向協調場景。掌握 countDown() 必須在 finally、await() 必須設超時、不可重用需換 CyclicBarrier/Phaser 三大鐵律,能避免 90% 的同步 Bug。現代 Java 開發中,CompletableFuture 與 StructuredTaskScope (Java 21+) 提供更具表達力的替代方案,建議優先考慮。