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

Java CountDownLatch 同步計數器:多執行緒協作等待完整解析與實戰應用

     編輯:Java 相關知識

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

特性CountDownLatchCyclicBarrierCompletableFuture
可重用性一次性可重置 (reset)一次性 (但可組合)
等待對象計數歸零所有執行緒到達屏障Future 完成
執行緒角色主/工作者分離對等參與者任務導向
異常處理無內建可設 barrierAction完善異常傳播
適用場景啟動等待、分階段同步迭代計算、並行聚合複雜非同步編排

進階技巧與注意事項

  1. 不可重置:需重複使用請改用 CyclicBarrier 或新建實例
  2. 計數不可增加:僅能 countDown(),建構後固定
  3. await() 可中斷:拋出 InterruptedException,需處理中斷狀態
  4. 超時等待:await(5, TimeUnit.SECONDS) 返回 false 表示超時
  5. 記憶體可見性:countDown() happens-before await() 返回

常見錯誤避坑

錯誤後果修正
countDown() 次數超過初始計數計數變負數,await 永不阻塞確保邏輯正確,或用 AtomicInteger 輔助
在 await() 前未啟動工作者主執行緒永久阻塞先啟動所有執行緒再 await
工作者異常未 countDown()主執行緒永久等待finally 區塊確保 countDown()
重複使用同一實例第二次 await 立即通過每階段新建實例
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved