CountDownLatch 的核心是基于 CAS 的 volatile 计数器同步机制,通过 await() 等待、countDown() 减计数实现前置任务完成后的统一触发,不保证线程同时启动,也不提供互斥保护。

CountDownLatch 的“门栓”机制不是靠锁住线程,而是通过计数器控制线程的等待与放行——它不保证任务“同时启动”,但能确保所有前置任务完成后再统一触发后续动作。
门栓的本质是计数器同步
CountDownLatch 内部维护一个 volatile int 类型的计数器。调用 countDown() 会使计数减一;调用 await() 的线程会阻塞,直到计数变为 0。这个过程不涉及加锁或抢占,而是基于 CAS(Compare-And-Swap)实现的原子操作,因此轻量且高效。
- 初始化时指定总数量(如
new CountDownLatch(3)),代表需等待的前置任务数 - 每个前置任务完成后调用一次
countDown(),计数递减 - 依赖这些任务的线程调用
await(),挂起直至计数归零
典型场景:多任务就绪后统一启动
比如启动一个服务时,需等数据库连接、配置加载、缓存预热三个模块都准备完毕,才能开启 HTTP 监听。这时可让主启动线程在关键节点前 await(),而三个模块各自完成工作后调用 countDown()。
- 即使三个模块执行时间不同,
await()线程也只会在最后一个模块完成时被唤醒 - 不存在“竞态启动”问题——没有一个模块完成就触发后续逻辑
- 注意:
await()可设超时,避免因某个模块异常导致永久阻塞
和 synchronized / ReentrantLock 的关键区别
CountDownLatch 不是互斥工具,也不控制临界区访问。它的“一致性”体现在**事件完成顺序的确定性**上,而非线程执行顺序的强制对齐。
立即学习“Java免费学习笔记(深入)”;
- 不能替代锁来保护共享变量,但可配合锁使用(例如在 countDown 前写入结果)
- 计数器归零后,所有等待线程被唤醒,但它们的 CPU 调度仍由操作系统决定,不保证严格同时执行
- 计数器不可重置,用完即弃;需重复使用应考虑 CyclicBarrier
常见误用提醒
容易把 CountDownLatch 当作“并发启动信号”,但实际上它只解决“等待完成”,不解决“同步开始”。若真需要多个线程在某一时刻精确并发执行,应结合 CyclicBarrier 或 Phaser。
- 不要在 await 后直接写业务逻辑而不检查中断状态(
Thread.interrupted()) - 避免在 countDown 中抛出未捕获异常,否则可能遗漏计数,导致 await 永久阻塞
- 确保每个前置任务都执行了 countDown,哪怕发生异常也要在 finally 块中调用


















