CountDownLatch 初始化必须传正整数,0则立即完成,负数抛IllegalArgumentException;任务数需预先确定且不可变;countDown()须在finally中调用;await()应使用带超时版本并处理中断与返回值;它是一次性组件,不可重置。

CountDownLatch 构造时必须传入正整数
如果传入 0,它会立即被判定为“已计数完成”,await() 不会阻塞;如果传入负数,会直接抛出 IllegalArgumentException。初始化任务数量必须在启动前就确定,且不能动态增减。
常见错误是把任务数算错——比如用 list.size() 却没校验 list 是否为 null,或在循环中重复 countDown() 导致过早释放主线程。
- 初始化时确保传入的值等于实际要等待的子任务数
- 避免在任务未真正启动前就调用
countDown() - 不要在同一个
CountDownLatch实例上多次调用await()后又复用(它不可重置)
每个子任务必须在 finally 块里调用 countDown()
子任务可能因异常、超时或逻辑分支提前退出,如果只在正常流程末尾写 countDown(),主线程就会永远卡在 await() 上。这是最常踩的坑。
典型场景:用 ExecutorService 提交多个 Runnable,每个都负责初始化一个组件(如数据库连接池、缓存客户端)。哪怕某个初始化抛了 RuntimeException,也得保证计数器减一。
executor.submit(() -> {
try {
initDatabase();
} catch (Exception e) {
log.error("DB init failed", e);
} finally {
latch.countDown(); // 关键:必须放 finally
}
});
await() 要处理超时和中断
生产环境不能让主线程无限等待。如果某个子任务卡死或网络异常,await() 无参版本会让整个应用 hang 住。必须用带超时的重载,并检查返回值。
另外,主线程若被中断(比如容器发来 shutdown 信号),await(long, TimeUnit) 会返回 false,此时应主动清理已初始化成功的资源,而不是继续等。
- 优先使用
latch.await(30, TimeUnit.SECONDS)而非latch.await() - 返回
false表示超时,需判断哪些子任务已完成、哪些失败,并决定是否继续启动主服务 - 如果主线程被中断,
await()会抛InterruptedException,记得重设中断状态:Thread.currentThread().interrupt()
CountDownLatch 不适合需要重复使用的场景
它是一次性的——一旦计数归零,所有后续 await() 都会立即返回 true,无法回到初始状态。如果你的初始化逻辑要支持热重载或周期性 reload,别硬套 CountDownLatch。
这时候更合适的是 CyclicBarrier(支持重置)或手动管理状态 + ReentrantLock + Condition。但要注意:CyclicBarrier 的语义是“所有线程互相等待到达某一点”,不是“主线程等子线程”,用法和协作模型不同。
简单说:只要需求是「一个线程等 N 个异步操作结束」,CountDownLatch 就是对的;但凡要第二次用,就得新建实例,别想着 reset。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











