閉鎖 是一種同步器:根據內部狀態調節線程控制流。像一扇門:計數器 > 0 時門關閉 (線程阻塞在 await());計數器 = 0 時門打開 (所有等待線程釋放)。
public class CountDownLatch {
public CountDownLatch(int count); // 初始化計數器
public void await() throws InterruptedException; // 阻塞直到計數=0
public boolean await(long timeout, TimeUnit unit); // 超時等待
public void countDown(); // 計數 -1,歸零喚醒所有等待者
public long getCount(); // 獲取當前計數
}
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count); // AQS state = count
}
public void countDown() {
sync.releaseShared(1); // AQS 模板方法
}
// Sync.releaseShared -> tryReleaseShared
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0) return false; // 已經歸零
int nextc = c - 1;
if (compareAndSetState(c, nextc)) { // CAS 修改 state
return nextc == 0; // 歸零返回 true,觸發 doReleaseShared 喚醒節點
}
}
}
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// Sync.tryAcquireShared
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1; // state=0 返回 >=0 成功,否則 -1 進入隊列阻塞
}
關鍵點:AQS 共享模式下,state=0 表示「門開」,所有節點依次被 doReleaseShared 喚醒,無需逐個 unpark,效率極高。
class Driver {
void main() throws InterruptedException {
CountDownLatch startGate = new CountDownLatch(1); // 起跑槍
CountDownLatch endGate = new CountDownLatch(N); // 終點
for (int i = 0; i < N; i++)
new Thread(new Worker(startGate, endGate)).start();
prepareEverything(); // 準備工作
startGate.countDown(); // 發令槍:所有 Worker 同時啟動
doSomethingElse(); // 主線程可做其他事
endGate.await(); // 等待所有 Worker 衝線
System.out.println("All done");
}
}
class Worker implements Runnable {
private final CountDownLatch startGate, endGate;
Worker(CountDownLatch start, CountDownLatch end) {
this.startGate = start; this.endGate = end;
}
public void run() {
try {
startGate.await(); // 等待發令
doWork(); // 核心任務
} catch (InterruptedException ignored) {
} finally {
endGate.countDown(); // 衝線
}
}
}
public class ServiceStartup {
static CountDownLatch latch = new CountDownLatch(3);
public static void main(String[] args) throws InterruptedException {
new Thread(() -> { checkDB(); latch.countDown(); }).start();
new Thread(() -> { checkRedis(); latch.countDown(); }).start();
new Thread(() -> { checkMQ(); latch.countDown(); }).start();
boolean ok = latch.await(30, TimeUnit.SECONDS); // 30s 超時保護
if (ok) System.out.println("所有依賴服務就緒,啟動業務系統");
else System.err.println("依賴服務啟動超時,啟動失敗");
}
// 模擬檢查...
}
| 坑點 | 後果 | 對策 |
|---|---|---|
| countDown 次數與初始 count 不符 | await 永不返回 / 過早返回 | 代碼審查、單元測試覆蓋計數邏輯 |
| Worker 異常導致 countDown 未執行 | 主線程永久阻塞 | finally 塊中調用 countDown |
| 重用 Latch | 構造函數不可重置,二次使用報錯 | 需重用請用 CyclicBarrier 或 Phaser |
| 無超時 await | 生產環境掛起無法恢復 | 必須使用 await(timeout, unit) |