CountDownLatch
大约 3 分钟
CountDownLatch 是基于 AQS 的 共享模式 (Shared Mode) 实现的。它的核心逻辑非常简单:利用 AQS 的 state 变量来充当倒计时计数器。
1. 核心原理概览
- 计数器存储:使用 AQS 的
state变量存储剩余的计数值(count)。 - 等待 (await):尝试获取“共享锁”。如果
state > 0,代表倒计时未结束,线程进入 AQS 队列阻塞等待。 - 倒数 (countDown):释放“共享锁”。每次将
state减 1。当state减为 0 时,触发 AQS 的共享节点唤醒机制(Propagate),唤醒队列中所有等待的线程。
2. 详细实现细节
2.1 初始化
new CountDownLatch(N) 会创建一个 AQS 的内部子类 Sync,并将 AQS 的 state 设置为 N。
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count); // 设置 state = count
}2.2 await() - 等待倒计时结束
调用 await() 本质上是调用 AQS 的 acquireSharedInterruptibly(1)。
- 尝试获取锁 (
tryAcquireShared):CountDownLatch重写了这个方法。- 逻辑极其简单:检查
state是否等于 0。 - 如果
state == 0,返回 1(成功,不阻塞)。 - 如果
state > 0,返回 -1(失败,进入 AQS 队列阻塞)。
// Sync 类中重写的 tryAcquireShared
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}- 入队阻塞:
- 如果
state > 0,当前线程会被封装成一个 SHARED 模式 的节点,放入 AQS 队列并挂起(Park)。
- 如果
2.3 countDown() - 倒数
调用 countDown() 本质上是调用 AQS 的 releaseShared(1)。
- 尝试释放锁 (
tryReleaseShared):CountDownLatch重写了这个方法。- 使用 CAS 自旋将
state减 1。 - 只有当
state从 1 变为 0 的那一瞬间,才会返回true。其他情况(比如从 5 变 4,或者本来就是 0)都返回false。
// Sync 类中重写的 tryReleaseShared
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0) return false; // 已经归零,无需操作
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0; // 只有减到 0 时才返回 true,触发唤醒
}
}- 触发唤醒 (
doReleaseShared):- 当
tryReleaseShared返回true时(即倒计时归零的瞬间),AQS 会调用doReleaseShared()。 - 这个方法会唤醒队列头部的线程。
- 当
2.4 关键点:唤醒传播 (Propagation)
这是 CountDownLatch 能同时唤醒所有 await 线程的关键:共享锁的传播机制。
- 当
state变为 0,第一个等待线程被唤醒。 - 它在
doAcquireSharedInterruptibly中醒来,再次检查tryAcquireShared,发现state == 0,获取成功。 - 核心步骤:获取成功后,它会执行
setHeadAndPropagate(node, r)。- 这个方法不仅把自己设为头节点,还会检查“后续节点是否也是共享模式”。
- 如果是,它会继续唤醒下一个节点。
- 下一个节点醒来,发现
state == 0,成功,继续唤醒再下一个…… - 这就形成了一个多米诺骨牌效应,队列中的所有共享节点会迅速被依次唤醒。
3. 总结
CountDownLatch 的实现非常精妙,它没有使用任何复杂的锁逻辑,完全依赖 AQS 的共享同步状态:
| 操作 | 对应 AQS 机制 | 逻辑 |
|---|---|---|
| 初始化 | setState(n) | 设定门闩的初始计数 |
| await() | acquireShared | 如果 state > 0 就排队睡觉;如果 state == 0 就通过 |
| countDown() | releaseShared | CAS 减 state。减到 0 时,触发“全部起床”信号 |
| 群起 | Propagate | 第一个醒来的线程会叫醒第二个,第二个叫醒第三个... |
一句话总结:CountDownLatch 就是一个 state 不为 0 就堵塞所有 await 线程,state 减为 0 就利用 AQS 共享传播机制“炸”开所有堵塞线程的开关。