深入理解 CountDownLatch:原理、使用與實戰場景
前言
在多線程并發編程中,協調線程間的執行順序是一個常見且關鍵的問題。CountDownLatch作為Java并發包中的重要工具類,為開發者提供了一種簡單高效的線程同步機制。
核心原理
CountDownLatch基于計數器機制實現:
- 初始化時指定一個非負整數作為計數器初始值,代表需要等待的事件數量
- 每當一個事件完成時,調用countDown()方法使計數器減1
- 等待線程通過await()方法阻塞自己,直到計數器值變為0才被喚醒
這種機制類似于倒計時,只有當所有預設的事件都完成后,等待線程才能繼續執行后續邏輯。
底層實現
CountDownLatch內部依賴AQS(AbstractQueuedSynchronizer)實現同步控制:
- 計數器值被維護在AQS的狀態變量中
- countDown()方法通過CAS操作減少狀態值
- await()方法會在狀態值非0時將線程加入等待隊列并阻塞
- 當狀態值變為0時,喚醒所有等待隊列中的線程
使用方法
CountDownLatch提供了簡單而直觀的API接口:
- 構造方法:public CountDownLatch(int count)初始化計數器值,count為需要等待的事件數量,必須是非負整數(為0 時await()方法會立即返回)。
- countDown():public void countDown()使計數器值減1,若減到0則喚醒所有等待線程。該方法無返回值,即使多次調用導致計數器為負也不會拋出異常。
- await():public void await() throws InterruptedException使當前線程進入等待狀態,直到計數器值為0或線程被中斷。
- 帶超時的await ():public boolean await(long timeout, TimeUnit unit) throws InterruptedException若在指定時間內計數器變為0則返回true,否則返回false。
使用示例
圖片
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 初始化計數器為3,代表需要等待3個線程完成
CountDownLatch latch = new CountDownLatch(3);
// 創建并啟動3個工作線程
for (int i = 0; i < 3; i++) {
int taskId = i;
new Thread(() -> {
try {
System.out.println("任務" + taskId + "開始執行");
// 模擬任務執行時間
Thread.sleep((long) (Math.random() * 1000));
System.out.println("任務" + taskId + "執行完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 任務完成,計數器減1
latch.countDown();
}
}).start();
}
System.out.println("主線程等待所有任務完成...");
// 主線程等待計數器變為0
latch.await();
System.out.println("所有任務已完成,主線程繼續執行");
}
}典型場景
在性能測試中,常需要同時啟動多個線程執行測試任務,CountDownLatch可精確控制所有線程同時開始:
public class ConcurrentTest {
private static final int THREAD_COUNT = 100;
private static CountDownLatch startLatch = new CountDownLatch(1);
private static CountDownLatch endLatch = new CountDownLatch(THREAD_COUNT);
public static void main(String[] args) throws InterruptedException {
// 創建100個測試線程
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
try {
// 等待開始信號
startLatch.await();
// 執行測試邏輯
performTest();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
endLatch.countDown();
}
}).start();
}
// 所有線程準備就緒后,發出開始信號
System.out.println("所有線程準備就緒,開始測試...");
startLatch.countDown();
// 等待所有測試線程完成
endLatch.await();
System.out.println("所有測試線程執行完畢");
}
private static void performTest() {
// 測試邏輯實現
}
}在應用啟動時,常需要加載多個組件或資源,主線程需等待所有初始化操作完成:
public class ApplicationInitializer {
public static void main(String[] args) throws InterruptedException {
// 需要初始化3個組件
CountDownLatch latch = new CountDownLatch(3);
// 初始化數據庫連接
new Thread(() -> {
initializeDatabase();
latch.countDown();
}).start();
// 初始化緩存
new Thread(() -> {
initializeCache();
latch.countDown();
}).start();
// 初始化配置
new Thread(() -> {
initializeConfig();
latch.countDown();
}).start();
// 等待所有初始化完成
latch.await();
System.out.println("所有組件初始化完成,應用啟動成功");
}
private static void initializeDatabase() { /* 數據庫初始化邏輯 */ }
private static void initializeCache() { /* 緩存初始化邏輯 */ }
private static void initializeConfig() { /* 配置初始化邏輯 */ }
}在處理批量任務時,可將任務分解為多個子任務并行處理,主線程等待所有子任務完成后進行結果匯總:
public class BatchProcessor {
public static void main(String[] args) throws InterruptedException {
List<Task> tasks = loadTasks(); // 加載任務列表
int threadCount = Runtime.getRuntime().availableProcessors();
CountDownLatch latch = new CountDownLatch(threadCount);
// 分配任務到多個線程
int batchSize = (tasks.size() + threadCount - 1) / threadCount;
for (int i = 0; i < threadCount; i++) {
int start = i * batchSize;
int end = Math.min(start + batchSize, tasks.size());
List<Task> subTasks = tasks.subList(start, end);
new Thread(() -> {
processSubTasks(subTasks); // 處理子任務
latch.countDown();
}).start();
}
latch.await();
System.out.println("所有任務處理完成,開始匯總結果");
// 匯總處理結果
}
private static List<Task> loadTasks() { /* 加載任務 */ return null; }
private static void processSubTasks(List<Task> tasks) { /* 處理子任務 */ }
}優缺點
- 優點
簡單易用:API設計簡潔,只需初始化計數器、調用countDown()和await()即可實現線程同步
高效性能:基于AQS實現,底層使用CAS操作,性能優異
靈活性高:支持多個線程等待和多個事件計數,適用場景廣泛
支持超時機制:可避免線程無限期等待
- 缺點
計數器不可重置:一旦計數器減到0,再次調用countDown()也不會產生任何效果,若需要重復使用需重新創建實例
無法知道進度:只能知道是否所有事件都完成,無法獲取部分完成的狀態
可能導致資源浪費:若countDown()未被正確調用,等待線程會一直阻塞
與其他同步工具的對比
同步工具 | 核心特點 | 核心特點 |
CountDownLatch | 計數器不可重置,一次性使用 | 等待多個線程完成后繼續執行 |
CyclicBarrier | 計數器可重置,支持重復使用 | 多個線程相互等待到達共同屏障點 |
Semaphore | 控制同時訪問資源的線程數量 | 資源有限的并發訪問控制 |
Exchanger | 兩個線程交換數據 | 線程間數據交換場景 |

































