countdownlatch用作压测启动开关时,需构造参数为1,所有工作线程调await()阻塞,主控线程调countdown()统一放行;它不可重用、无就绪回调,且await被中断后须恢复中断状态。

CountDownLatch怎么让所有线程等一个信号才开始跑
用 CountDownLatch 做压力测试的“启动开关”,核心就两步:构造时设为 1,所有工作线程调用 await() 阻塞,主控线程最后调一次 countDown()。它不是锁,不涉及竞争资源,只管“数归零就放行”。
常见错误是把 CountDownLatch 和 CyclicBarrier 混用——后者适合多轮重复同步,而压测通常只要一轮齐发;还有人误以为 await() 能被中断后自动重试,其实一旦抛出 InterruptedException,线程就退出了,得自己处理恢复逻辑。
- 构造参数必须是
1(不是线程数),否则起不到“统一开关”作用 - 每个工作线程必须在真正执行业务逻辑前调用
await(),不能漏、不能晚 - 主控线程调
countDown()前,要确保所有工作线程已进入await()状态,否则可能漏掉部分线程
为什么不能用Thread.sleep()或volatile布尔变量代替
Thread.sleep(100) 看似简单,但精度差、易漂移,100ms 内总有线程提前或滞后执行,压测结果抖动大;volatile boolean 看似轻量,却无法保证“所有线程看到 true 的那一刻,都刚好准备好执行”,存在竞态:有的线程读到 true 后立刻执行,有的还在初始化数据库连接。
CountDownLatch 的底层依赖 AQS 的 park()/unpark(),能精确控制线程挂起与唤醒时机,且 await() 是可响应中断的阻塞,比自旋或休眠更省资源。
立即学习“Java免费学习笔记(深入)”;
-
volatile变量无法阻塞线程,需配合循环轮询,浪费 CPU -
Thread.sleep()时间不可控,尤其在高负载机器上误差常超 ±20ms -
CountDownLatch的await(long, TimeUnit)还能防死锁,超时后可主动报错退出
压测场景下CountDownLatch.await()被中断怎么办
压测脚本常需要支持 Ctrl+C 中断,这时主线程可能被 interrupt(),导致工作线程在 await() 时抛出 InterruptedException。不处理的话,线程直接退出,压测数据不全,还可能留下未关闭的连接。
正确做法是在捕获异常后,显式恢复中断状态,并决定是否继续执行业务逻辑——多数压测框架选择“中止当前线程”,但得确保资源清理(如关闭 HTTP 客户端)。
- 必须调用
Thread.currentThread().interrupt()恢复中断标记,否则上层无法感知中断 - 不要在
catch块里吞掉异常又不恢复中断,这是最常见也最隐蔽的坑 - 如果压测允许“部分线程执行”,可在 catch 后直接运行业务逻辑,但需记录 warn 日志说明偏差
try {
latch.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 关键!
log.warn("Thread interrupted before test start");
return;
}
// 此处才开始发请求
和Phaser、CyclicBarrier比,CountDownLatch在压测里有什么硬伤
CountDownLatch 最大的限制是“一次性”:计数器归零后,后续调用 await() 会立即返回,无法重复用于多轮压测。如果你要做“预热 → 正式压测 → 再压测”这种流程,每次都要 new 一个新的实例,而 Phaser 或 CyclicBarrier 支持重置。
另外,它不提供“到达屏障点”的回调,没法在所有线程就位时自动打印日志或采集系统指标——得靠主控线程在 countDown() 前手动加日志,容易遗漏。
- 多轮压测必须重建
CountDownLatch实例,注意别复用已触发过的对象 - 没有内置的“就绪通知”机制,想统计就绪线程数,得自己维护
AtomicInteger - Android 或老版本 JDK(
Thread.currentThread().interrupt(),漏掉它,整个压测脚本就变成“看起来能停,实际停不干净”。










