首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java 並發核心:CountDownLatch 閉鎖原理、源碼解析與最佳實踐

     編輯:Java 程式設計入門

什麼是閉鎖?

閉鎖 是一種同步器:根據內部狀態調節線程控制流。像一扇門:計數器 > 0 時門關閉 (線程阻塞在 await());計數器 = 0 時門打開 (所有等待線程釋放)。

應用場景

  1. 主線程等待多個初始化任務 (緩存預熱、配置加載、服務註冊) 完成再啟動服務。
  2. 並行計算分階段:階段 1 所有任務完成,才能進入階段 2 (此時 CyclicBarrier 更合適)。
  3. 壓力測試:主線程等待所有請求線程發送完畢,再統計結果。
  4. 死鎖檢測模擬:控制併發線程數嘗試觸發死鎖。

CountDownLatch 核心 API


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();                     // 獲取當前計數
}

源碼核心邏輯 (基於 AQS 共享模式)

初始化


public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count); // AQS state = count
}

countDown():釋放共享鎖


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 喚醒節點
        }
    }
}

await():獲取共享鎖


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,效率極高。

標準使用模式 (Driver-Worker)


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("依賴服務啟動超時,啟動失敗");
    }
    // 模擬檢查...
}

常見坑與最佳實踐

總結

CountDownLatch 是一次性、高效、基於 AQS 共享鎖的閉鎖工具。核心價值:將「等待多個異步事件完成」轉化為「等待一個計數器歸零」。掌握其源碼 CAS 修改 state、共享鎖喚醒機制,能從根本上理解 Java 同步器設計哲學。生產代碼務必加超時、finally 保護、避免重用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
坑點後果對策
countDown 次數與初始 count 不符await 永不返回 / 過早返回代碼審查、單元測試覆蓋計數邏輯
Worker 異常導致 countDown 未執行主線程永久阻塞finally 塊中調用 countDown
重用 Latch構造函數不可重置,二次使用報錯需重用請用 CyclicBarrier 或 Phaser
無超時 await生產環境掛起無法恢復必須使用 await(timeout, unit)