countdownlatch 的计数器不主动感知线程,而是通过 aqs 的 volatile state 和 cas 原子操作控制线程阻塞与唤醒:await() 时线程入队并 park,countdown() 归零后遍历队列 unpark 所有等待线程。

CountDownLatch 的核心计数器本身不直接“感知”线程,而是通过 AQS(AbstractQueuedSynchronizer)的 state 字段 + 等待队列机制 实现对线程阻塞与唤醒的精确控制。所谓“线程感知”,本质是线程在调用 await() 时主动加入 AQS 同步队列,而计数器归零时由 countDown() 触发队列中所有等待线程的唤醒 —— 这个过程由 JVM 线程调度配合 AQS 的原子状态变更共同完成,而非计数器主动追踪线程。
计数器基于 AQS 的 state 原子操作
CountDownLatch 内部持有一个 Sync 子类(继承 AQS),其计数值完全映射到 AQS 的 state 变量:
-
state初始化为构造时传入的 count,每次countDown()调用都执行compareAndSetState(current, current - 1),保证减操作的原子性 -
await()判断是否继续阻塞,只看getState() == 0;不关心谁修改了它,只响应结果 - 没有线程 ID 记录、不维护线程引用,纯粹靠 state 值驱动行为
线程挂起与唤醒由 AQS 队列自动管理
当线程调用 await() 且 state > 0 时,并非轮询等待,而是:
- 构造一个
Node节点,以独占模式加入 AQS 的 FIFO 同步队列 - 调用
LockSupport.park()使当前线程进入 WAITING 状态,JVM 将其挂起 -
countDown()每次成功减 state 后,若发现state == 0,则遍历同步队列,对每个等待节点调用LockSupport.unpark(node.thread)
唤醒不依赖计数器“记住”线程,而是逐个通知队列节点
关键点在于:AQS 不要求计数器知道有哪些线程在等,它只做两件事:
- 阻塞时:把调用
await()的线程封装成 Node,连入队列,然后 park - 归零时:从队头开始,逐个 unpark 所有处于等待状态的 Node 对应线程
- 即使多个线程同时 await,它们被依次加入队列;即使部分线程已中断,AQS 也会跳过无效节点,保障安全唤醒
没有竞态,因为所有关键路径都受 CAS 和 volatile 保护
整个流程不依赖锁或 synchronized 块,而是依靠:
-
state是 volatile int,保证可见性 -
compareAndSetState使用 Unsafe CAS,确保计数更新无锁且原子 - 队列操作(入队/出队/唤醒)均在 AQS 内部加锁(acquire/release 的模板逻辑),避免并发修改队列结构
不是计数器在感知线程,而是线程主动登记、AQS 统一调度 —— 这种设计轻量、高效,也正因如此,CountDownLatch 一旦触发就不能重置,它的“感知”是一次性的协作契约。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











