countdownlatch 比 wait/notify 更安全可靠,因其线程安全、无锁、避免虚假唤醒和死锁;基于 aqs 的 cas 操作保证原子性与可见性;await/countdown 语义明确、声明式、支持超时中断及一次性事件语义。

因为 countDown() 递减计数器 是一个线程安全、无锁、可组合、且天然避免虚假唤醒和死锁的信号机制,而直接用 notify() 唤醒依赖手动加锁、易出错、且无法保证唤醒时机与状态一致。
CountDownLatch 的信号本质是“事件完成”,不是“通知某人”
闭锁关注的是“某个条件是否达成”,比如“后台任务是否结束”“初始化是否完成”。countDown() 表示“这件事完成了”,await() 表示“我等这件事完成”。这个逻辑是声明式的、面向结果的。而 notify() 是面向线程的、命令式的——它只说“叫醒一个在等的人”,但不保证那人该醒、那人等的条件真满足了。
例如:
- 多个线程调用
await()等待服务启动完成,只需一次countDown()就能全部释放; - 若改用
notify(),必须确保有且仅有一个线程在wait(),且唤醒前要synchronized同一对象,否则抛IllegalMonitorStateException; - 一旦漏调
notify()或多调,或等待者还没进入wait()就发了通知,就会永久阻塞或丢失信号。
递减操作天然具备原子性与内存可见性
CountDownLatch 底层基于 AQS(AbstractQueuedSynchronizer),countDown() 调用的是 compareAndSetState() 这类 CAS 操作,保证计数器更新的原子性,同时借助 volatile 语义自动实现跨线程的内存可见性。不需要开发者手动加锁、也不用担心指令重排序导致的条件判断失效。
而 wait()/notify() 必须包裹在 synchronized 块中,锁的获取/释放本身就有开销,且容易因锁对象不一致、忘记加锁、或异常提前退出导致 notify() 未执行——这些都会让等待线程永远卡住。
规避虚假唤醒和条件重检陷阱
await() 内部已内置了对中断、超时、以及计数器归零的完整判断逻辑,无需用户写 while (count != 0) wait(); 这样的循环。而手写 wait()/notify() 时,必须用 while 循环重检条件,否则可能因虚假唤醒(spurious wakeup)导致线程在条件未满足时继续执行,引发数据错乱。
比如:一个线程 wait() 等待缓冲区非空,被唤醒后没检查就直接读,结果缓冲区仍是空的——这就是典型的条件未重检导致的 bug。
支持超时、中断、一次性语义,更贴近实际需求
await(long timeout, TimeUnit unit) 和 await() 可响应中断,这是 wait() 单独做不到的(wait() 虽也能响应中断,但需配合复杂的状态管理)。而且 CountDownLatch 的“一次性”设计(计数器归零即不可逆)明确表达了“事件只发生一次”的业务语义,比靠人工控制 notify() 调用次数更可靠。
相比之下,notify() 本身不携带任何上下文,不能区分“第一次初始化完成”和“第二次重载完成”,也不能告诉等待者“你等的到底是哪个事件”。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











