应使用 CountDownLatch 替代 Thread.sleep():它通过 await() 阻塞等待、countDown() 主动通知实现精准、可测、响应及时的线程协调,避免硬编码等待时间导致的不可靠问题。

别再用 Thread.sleep() 硬等了——它既不精准、又难维护,还容易掩盖真正的并发问题。用 CountDownLatch 替代“睡大觉”式等待,能让代码逻辑清晰、响应及时、可测性强。
为什么 sleep 是个坏主意
老旧代码里常见这种写法:
new Thread(() -> {doStep1();
Thread.sleep(5000); // 等服务启动?等文件生成?靠猜!
doStep2();
}).start();
问题很明显:
• 睡太短,条件没就绪,执行失败;
• 睡太长,白白浪费时间,拖慢流程;
• 无法感知真实状态,一出错就静默卡死;
• 单元测试几乎没法 mock 或加速。
用 CountDownLatch 把“被动等”变成“主动通知”
核心思路:不是“我等 5 秒”,而是“我等你喊我一声”。把耗时操作的完成作为信号,由执行方主动倒数归零,等待方被唤醒。
重构三步走:
-
声明一个 latch(通常 1 个或 N 个):比如
CountDownLatch latch = new CountDownLatch(1); - 在等待处调用 await():线程会阻塞,直到 count 变为 0 或被中断
- 在条件满足时调用 countDown():比如回调里、监听器中、异步任务结尾——只发一次信号
典型场景改写示例
场景:启动 Web 容器后,再初始化业务组件
// 改前(危险)startEmbeddedServer();
Thread.sleep(3000); // 假设够了?网络慢就崩
initBusinessModules();
改后(可靠)
// 初始化 latchCountDownLatch serverReady = new CountDownLatch(1);
// 启动容器,并注册就绪回调
startEmbeddedServer(() -> serverReady.countDown());
// 主线程等待,超时也安全
if (!serverReady.await(10, TimeUnit.SECONDS)) {
throw new RuntimeException("Server failed to start in time");
}
initBusinessModules();
关键点:
• await() 支持超时,不会永久挂起;
• countDown() 是幂等的,多调几次也没副作用;
• 所有等待线程会在同一时刻被唤醒,天然适合广播型协调。
避坑提醒:几个易错细节
CountDownLatch 不是万能锁,用错反而更乱:
-
不能重置:一旦 count 减到 0,后续
await()直接通过——需要重复使用请换CyclicBarrier - 别在 finally 外漏掉 countDown():异常路径也要确保通知,否则 await 永远卡住
- 避免在 await 前就 countDown:竞态下可能让 await 瞬间返回,逻辑错乱;推荐先启动异步任务,再 await
- 注意线程可见性:latch 本身保证同步语义,但 countDown 前修改的共享变量,仍需额外同步(如 volatile)

















