在多線程開發中,常會遇到「主線程需等待多個子線程完成任務後再繼續執行」的場景。從 JDK 1.5 起,java.util.concurrent.CountDownLatch 提供了一種優雅的同步機制來解決此類問題。本文將深入淺出地介紹其核心原理、關鍵方法及實戰應用。
CountDownLatch 內部維護一個計數器,初始值由建構子傳入的 int 參數決定。它有兩個核心操作:
此外,還提供帶超時的 await(long timeout, TimeUnit unit),若超時則不再阻塞,無論計數器是否歸零。
假設一輛車需等 10 位同學上車,設定等待 3 秒。若 3 秒內全上齊則發車;否則時間到直接開走,剩下的同學只能看著車尾燈發呆。
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class CountDownLatchTest {
public static int numberOfPeople = 10;
public static volatile boolean isGone = false; // 使用 volatile 保證可見性
public static int carWaitTime = 3;
public static void main(String[] args) throws InterruptedException {
CountDownLatch waitStudentsGetOn = new CountDownLatch(numberOfPeople);
// 啟動上車線程
new Thread(new GetOn(waitStudentsGetOn)).start();
// 主線程等待上車
waitStudentGetOn(waitStudentsGetOn);
// 發車
driveHome();
}
private static void waitStudentGetOn(CountDownLatch latch) throws InterruptedException {
System.out.println("趕緊的,抓緊時間上車..");
// 等待 3 秒,時間到自動返回
latch.await(carWaitTime, TimeUnit.SECONDS);
}
private static void driveHome() throws InterruptedException {
System.out.println("開車走囉~ (鞋兒破 帽兒破...)");
isGone = true;
}
}
class GetOn implements Runnable {
private final CountDownLatch latch;
GetOn(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
for (int i = 0; i < CountDownLatchTest.numberOfPeople; i++) {
try {
// 若車已走,剩餘同學無法上車
if (CountDownLatchTest.isGone) {
System.out.println("媽的,還差:" + latch.getCount() + " 個沒娃上車呢,車走了!");
break;
}
boolean success = new Student(i + 1).getOn();
if (success) {
latch.countDown(); // 計數器減一
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
// 即時反饋剩餘人數
if (latch.getCount() != 0) {
System.out.println("還差:" + latch.getCount() + " 個沒上車");
} else {
System.out.println("都上車了!");
}
}
}
static class Student {
private final int id;
Student(int id) { this.id = id; }
boolean getOn() throws InterruptedException {
// 模擬上車耗時 0~1 秒
Thread.sleep(new Random().nextInt(2) * 1000L);
if (CountDownLatchTest.isGone) return false;
System.out.print("編號為:" + id + "的同學上車了.. ");
return true;
}
}
}
isGone 標記需加 volatile,確保多線程間的可見性,避免指令重排導致邏輯錯誤。await() 和 sleep() 會拋出 InterruptedException,捕獲後應恢復中斷狀態 (Thread.currentThread().interrupt()) 或退出循環,否則中斷信號會丟失。CountDownLatch 計數器歸零後無法重置,若需循環使用請改用 CyclicBarrier。CountDownLatch 以極簡的 API 解決了「一對多」線程同步等待問題,適用於應用啟動初始化、並行任務聚合等場景。掌握其「倒數鎖」語義,能讓並發控制代碼更清晰、健壯。