countdownlatch 是 java 中用于线程协调的轻量级同步工具,通过构造时指定非负计数器,使主线程等待多个子线程完成后再继续执行;其 await() 阻塞、countdown() 减计数,基于 aqs 实现,不可重置、不感知任务成败,适用于一次性等待场景。

CountDownLatch 是 Java 并发编程中协调多线程执行顺序最常用、最直观的工具之一。它不负责任务调度,也不管理线程生命周期,而是专注解决一个核心问题:让一个线程(通常是主线程)可靠地等待多个其他线程全部完成后再继续执行。它的设计轻量、语义清晰、线程安全,且底层基于 AQS 实现,性能稳定。
怎么初始化和设置计数器
CountDownLatch 的初始计数值代表“需要等待完成的任务数量”,必须在构造时明确指定,且只能是非负整数。
- 值为 0:创建后立即满足条件,所有
await()调用会直接返回,适合用于“无需等待”的边界场景或测试 - 值为 N(N > 0):表示需等待 N 次
countDown()调用才能触发唤醒;这个 N 通常等于你启动的子线程数,或需完成的独立子任务数 - 值不能为负:传入负数会抛出
IllegalArgumentException
示例:CountDownLatch latch = new CountDownLatch(5); 表示主线程将等待 5 个子任务全部调用 countDown() 后才继续。
await() 和 countDown() 怎么配合工作
await() 是阻塞点,countDown() 是释放信号——二者通过共享计数器联动,但彼此解耦:
-
await()在当前线程调用,若计数未归零,线程进入 WAITING 状态并挂起;一旦计数变为 0,线程被唤醒并立即向下执行 -
countDown()可由任意线程调用,每次调用以原子方式将计数减 1;当减至 0 时,AQS 会一次性唤醒所有因await()而阻塞的线程 - 多个线程可同时调用
await(),它们都会被统一阻塞和唤醒;多个线程也可并发调用countDown(),无竞争风险 - 推荐在
finally块中调用countDown(),确保即使任务异常退出,计数也能正常减少,避免主线程永久阻塞
典型实战场景与写法要点
CountDownLatch 最常用于三类协调场景,写法有共性也有细节差异:
-
主线程等子线程全部初始化完成:如服务启动时加载配置、连接池、缓存等,每个初始化任务在一个线程中执行,完成后调用
countDown();主线程在最后调用await(),再启动主服务逻辑 -
并行计算后汇总结果:将大任务拆成 N 份,分发给 N 个线程处理,每份结果存入共享容器(如
ConcurrentHashMap或带索引的数组),各线程结束前countDown();主线程await()后统一读取并合并 -
模拟高并发请求压测:主线程先启动所有工作线程但不执行,用一个
CountDownLatch startLatch = new CountDownLatch(1)控制“同时出发”;所有线程调用startLatch.await()阻塞;主线程调用一次startLatch.countDown(),所有线程瞬间并发启动
注意:不要混用 join() 和 CountDownLatch 做同一目的——前者耦合线程对象,后者解耦任务逻辑,更灵活也更易维护。
使用时容易忽略的关键限制
CountDownLatch 是一次性同步器,这点决定了它适用范围和替代方案:
-
不可重置:计数归零后,
await()不再阻塞,countDown()继续调用也无效果;若需循环等待(如批次处理),应改用CyclicBarrier -
无超时默认行为:
await()可能无限等待,生产环境建议优先使用带超时的重载await(long timeout, TimeUnit unit),避免因某个子任务卡死导致整个流程挂起 - 不感知任务成败:它只管“是否调用了 countDown”,不管任务是成功、失败还是被中断;业务上需自行捕获异常、记录日志或设置状态标志位
-
不保证执行顺序:子线程间执行顺序不确定,
countDown()的调用顺序也不影响唤醒逻辑——只要总数达标即唤醒
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











