在 Java 并发编程中,CountDownLatch 是一个非常实用的同步辅助类,常用于协调多个线程之间的执行顺序,例如让主线程等待若干工作线程全部完成后再继续执行。而其底层实现的核心,正是 AQS(AbstractQueuedSynchronizer)。
本文将深入剖析 CountDownLatch 如何基于 AQS 构建自己的同步逻辑,并揭示 AQS 在并发工具类中的典型使用模式。
一、AQS 的通用使用三步法
在理解 CountDownLatch 之前,先掌握 AQS 的标准使用范式至关重要。通常,若要基于 AQS 实现自定义同步器,需遵循以下三步:
- 定义内部 Sync 类
创建一个内部类(通常命名为 Sync),继承 AbstractQueuedSynchronizer。
- 重写获取/释放方法
根据同步器是“独占”还是“共享”模式,重写对应的方法:
- 独占模式:
tryAcquire()/tryRelease()
- 共享模式:
tryAcquireShared()/tryReleaseShared()
- 封装对外 API
在主类中提供用户友好的方法(如 await()、countDown()),并在内部调用 AQS 的模板方法(如 acquireSharedInterruptibly()、releaseShared())。
⚠️ 注意:AQS 中的 tryXXX 方法默认抛出 UnsupportedOperationException,因此子类必须重写相关方法,否则运行时会报错。
二、CountDownLatch 的 AQS 实现解析
2.1 内部 Sync 类定义
CountDownLatch 的核心逻辑封装在其私有静态内部类 Sync 中:
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count); // 初始化 AQS 的 state
}
int getCount() {
return getState(); // 获取剩余计数
}
protected int tryAcquireShared(int acquires) {
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;
}
}
}
- state 的含义:表示“还需倒数的次数”。初始值由构造函数传入。
- 共享模式:因为多个线程可以同时“通过门闩”(即 await 成功),所以使用共享锁语义。
2.2 构造函数:初始化倒计数值
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
- 将用户指定的
count 传递给 Sync 构造函数。
Sync 调用 setState(count),将 AQS 的 state 初始化为倒计数值。
2.3 await():等待门闩打开
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
- 调用 AQS 的
acquireSharedInterruptibly 方法。
- 该方法首先检查中断状态,然后调用
tryAcquireShared:
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
- 若
state == 0,说明倒数已完成,返回 1(表示获取成功),线程继续执行;
- 否则返回
-1,AQS 会将当前线程加入等待队列并阻塞,直到被唤醒。
2.4 countDown():减少计数并尝试开门
public void countDown() {
sync.releaseShared(1);
}
- 调用 AQS 的
releaseShared,内部会调用 tryReleaseShared:
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; // 只有归零时才返回 true
}
}
}
- 使用 CAS 原子地将
state 减 1;
- 仅当
nextc == 0 时返回 true,此时 AQS 会调用 doReleaseShared(),唤醒所有等待线程。
✅ 关键点:只有最后一次 countDown() 才会触发线程唤醒,避免重复唤醒开销。
三、工作流程总结
- 初始化:
new CountDownLatch(N) → AQS 的 state = N。
- 等待:调用
await() 的线程检查 state:
- 若
state > 0,进入 AQS 队列阻塞;
- 若
state == 0,直接放行。
- 倒数:每次
countDown() 将 state 原子减 1。
- 开门:当
state 减至 0 时,AQS 唤醒所有阻塞线程,后续 await() 调用不再阻塞。
四、为何选择 AQS?
Doug Lea 在设计 AQS 时,刻意采用“继承 + 重写”而非“接口实现”,原因在于:
- 接口要求实现所有方法,但不同同步器只需部分功能;
- 继承允许按需重写,更灵活;
- 默认方法抛出异常,强制子类明确行为,避免误用。
这种设计使得 AQS 成为一个高度可复用、安全且高效的同步框架。
五、扩展思考:其他工具类的 AQS 应用
- Semaphore:
state 表示许可数量,acquire() 对应 acquireShared,release() 对应 releaseShared。
- ReentrantLock:使用独占模式,
state 表示重入次数。
- CyclicBarrier:虽然也用于线程协调,但不基于 AQS,而是通过
ReentrantLock + Condition 实现。
因此,CountDownLatch 是 AQS 共享模式的经典范例。
结语
通过 CountDownLatch 的源码分析,我们不仅理解了其工作机制,更掌握了 AQS 在实际并发工具中的应用范式。AQS 将复杂的线程排队、阻塞、唤醒等底层细节封装起来,让开发者只需关注业务状态(如倒计数)的转换逻辑,极大提升了开发效率与系统稳定性。深入理解这些并发工具背后的Java并发框架,是通往高级工程师的必经之路。
掌握 AQS,就等于握住了 Java 并发世界的“万能钥匙”。如果你想了解更多关于并发编程的知识,欢迎来云栈社区与更多开发者一同交流探讨。