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

Java CountDownLatch 同步工具類完整指南:多執行緒協調實戰

     編輯:Java 相關知識

前言

CountDownLatch 是 Java 5 引入的同步工具,以計數器機制實現「一個或多個執行緒等待其他執行緒完成操作」的協調模式。本文詳細說明核心 API、典型應用場景(服務啟動、並行計算、測試協調)、進階用法與常見陷阱。

核心機制與 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+) 提供更具表達力的替代方案,建議優先考慮。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved