CountDownLatch 是 Java 併發包中基於 AQS(AbstractQueuedSynchronizer)共享模式實現的一次性計數器閂。本文基於 JDK 1.8 源碼,從結構設計、await() 阻塞流程、同步隊列節點管理三個維度,深度剖析其核心實現原理。
public class CountDownLatch {
// 內部類:繼承 AQS,實現共享鎖邏輯
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count); // state 即計數器值
}
int getCount() { return getState(); }
// 共享鎖獲取嘗試:state==0 返回 1(成功),否則 -1(失敗)
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// 共享鎖釋放嘗試:CAS 遞減 state,歸零返回 true 觸發喚醒
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0) return false;
int nextc = c - 1;
if (compareAndSetState(c, nextc)) // CAS 保證原子性
return nextc == 0; // 歸零才真正釋放共享鎖
}
}
}
private final Sync sync;
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
// 阻塞等待(可中斷)
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// 超時等待
public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
// 計數器遞減
public void countDown() {
sync.releaseShared(1);
}
public long getCount() { return sync.getCount(); }
}
volatile int state 繼承自 AQS,保證多線程可見性state 不再變化,後續 await() 直接返回public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted()) throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
tryAcquireShared(1) 呼叫 Sync.tryAcquireShared,state==0 返回 1(成功),直接返回;否則返回 -1,進入排隊邏輯private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) { // CAS 入隊
pred.next = node;
return node;
}
}
enq(node); // 隊列為空或 CAS 失敗時初始化/重試
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // 隊列未初始化
if (compareAndSetHead(new Node())) // 建立哨兵頭節點
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) { // CAS 追加尾節點
t.next = node;
return t;
}
}
}
}
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) { // 自旋等待
final Node p = node.predecessor();
if (p == head) { // 成為首節點(繼承頭節點)
int r = tryAcquireShared(arg); // 再次嘗試獲取
if (r >= 0) {
setHeadAndPropagate(node, r); // 設置新頭,傳播喚醒後續節點
p.next = null; // help GC
failed = false;
return; // 成功返回
}
}
// 非首節點或獲取失敗:判斷是否應掛起,掛起並檢查中斷
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
throw new InterruptedException(); // 中斷拋出異常
}
} finally {
if (failed) cancelAcquire(node); // 異常退出時取消節點
}
}
setHeadAndPropagate 設置新頭節點,若 r > 0(共享模式成功)則喚醒後續節點parkAndCheckInterrupt() 調用 LockSupport.park() 掛起,被中斷時返回 true 拋出 InterruptedExceptionfinally 中 cancelAcquire 將節點從隊列移除,避免內存洩漏static final class Node {
volatile int waitStatus; // 節點狀態:SIGNAL, CANCELLED, CONDITION, PROPAGATE
volatile Node prev, next;
volatile Thread thread;
Node nextWaiter; // Condition 隊列用
// 構造方法...
}
shouldParkAfterFailedAcquire(pred, node):前驅狀態為 SIGNAL 返回 true;若前驅 CANCELLED 向前清理;否則 CAS 設置前驅為 SIGNALparkAndCheckInterrupt():LockSupport.park(this) 掛起當前線程,返回中斷狀態setHeadAndPropagate(node, propagate):設置新頭,propagate > 0 時調用 doReleaseShared() 喚醒後續共享節點| 方案 | 缺陷 | CountDownLatch 優勢 |
|---|---|---|
Thread.join() |
需持有線程引用,大量線程管理繁瑣;不支持超時精確控制 | 無需線程引用,計數器抽象,支持超時/中斷 |
synchronized + wait/notify |
需手動維護條件變量,易出現虛假喚醒、通知遺漏;鎖粒度粗 | AQS 內建隊列+狀態機,無鎖 CAS 更新 state,性能優 |
volatile boolean flag + 自旋 |
CPU 佔用高,無法掛起線程 | 條件不滿足時掛起,條件滿足批量喚醒,CPU 友好 |
Sync extends AQS,state 為計數器tryAcquireShared 失敗 → 入隊 → 自旋 → 掛起 → 被喚醒/中斷返回下篇將深度解析:
countDown() → tryReleaseShared CAS 遞減 → 歸零觸發 doReleaseShared 批量喚醒流程CyclicBarrier、Phaser、CompletableFuture 選型指南