CountDownLatch 是 Java 并发包里典型的同步辅助类,它的核心用途是让一个或多个线程等待其他线程完成一组操作。很多人只停留在 countDown 和 await 的 API 表面,却不清楚它为什么能在大批量任务对齐场景中保持正确性与高性能。其实它的全部能力都建立在 AbstractQueuedSynchronizer(AQS)的 Shared 模式之上。

一、AQS Shared 模式的基本机制
AQS 内部维护了一个 volatile 修饰的同步状态 state 以及一个 CLH 变体的等待队列。与 Exclusive 模式不同,Shared 模式允许多个线程同时获取资源。在 Shared 模式下,线程通过 acquireShared 方法尝试获取同步状态,如果返回值大于等于 0 表示获取成功,否则线程会被包装成节点加入队列并挂起。
当某个线程调用 releaseShared 释放资源时,AQS 会修改 state 并尝试唤醒队列中的后续节点。关键点在于,Shared 模式的唤醒过程使用了传播机制:如果一个节点被唤醒并成功获取共享资源,它会继续尝试唤醒下一个共享节点,从而保证在 state 满足条件时,所有等待线程都能被放行,而不会出现只唤醒一个的遗漏情况。
// AQS 中共享模式释放的典型逻辑片段(简化)
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
二、CountDownLatch 对 AQS 的 Shared 实现
CountDownLatch 内部有一个私有的 Sync 类继承自 AQS,并将构造时传入的计数作为初始 state。tryAcquireShared 方法判断 state 是否为 0,只有为 0 时才返回 1,否则返回 -1,这表示只要计数没归零,调用 await 的线程就必须排队等待。
tryReleaseShared 则每次将 state 减 1,并使用 CAS 保证线程安全。当减到 0 时返回 true,触发 AQS 的 doReleaseShared 唤醒流程。由于是 Shared 模式,所有在队列里等待的线程会被连续唤醒,从而达成“任务全部完成,主流程统一继续”的对齐效果。
// CountDownLatch 内部 Sync 的核心实现(简化)
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);
}
protected int tryAcquireShared(int acquires) {
// 状态为 0 才能获取,否则进入等待
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0) {
return false;
}
int nextc = c - 1;
if (compareAndSetState(c, nextc)) {
return nextc == 0;
}
}
}
}
三、任务对齐中的同步原理剖析
所谓任务对齐,是指主线程必须等 N 个并行子任务都执行到某个边界点后再统一推进。使用 CountDownLatch 时,主线程调用 await 进入 AQS 共享队列;每个子任务结束前调用 countDown,使 state 递减。由于 state 的递减和可见性由 AQS 的 volatile 与 CAS 保障,因此即使高并发提交任务,计数也不会错乱。
当最后一个子任务调用 countDown 将 state 置为 0,tryReleaseShared 返回 true,AQS 开始共享传播唤醒。此时队列中所有的 await 线程会依次被 unpark,并重新检查 tryAcquireShared,发现 state 为 0 后全部放行。这种“一次性全放行”的特性,正是 Shared 模式相对于 Exclusive 模式在任务对齐上的最大优势,也避免了用循环轮询或多次加锁带来的开销。
四、与其他方案的对比及使用注意
若使用 Thread.join,主线程必须顺序等待每个子线程结束,无法真正并行对齐;若用 CyclicBarrier,则侧重于多线程互相等待再同时出发,且可重用,而 CountDownLatch 不可重置,更适合一次性任务对齐。理解它们底层分别是 AQS Shared 或独占配合条件队列,有助于在设计中正确选型。
在实际使用中,要避免在 countDown 前抛出未捕获异常导致计数不归零,从而使 await 线程永久挂起。推荐将 countDown 放在 finally 块中,或结合 await 的超时重载方法保护系统。通过掌握 AQS Shared 的底层链路,我们不仅能用好 CountDownLatch,也能更快理解 Semaphore 等同类工具的运转逻辑。
// 安全的任务对齐示例
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
// 模拟子任务
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown();
}
}).start();
}
latch.await(); // 主线程等待三个任务全部结束
AQSCountDownLatchshared_mode修改时间:2026-08-01 12:15:27