双 CountDownLatch 经典并发压测模型(启动门+完成门)
一、模型原理
两个Latch分工明确:
-
startLatch(启动门,初始计数=1)
所有工作线程启动后统一阻塞在start.await(),等待主线程统一开门,保证N个线程近乎同时并发执行,消除线程启动时差。
主线程调用start.countDown()一次性放行所有线程。 -
doneLatch(完成门,初始计数=N)
每个工作线程执行完毕后执行done.countDown();
主线程阻塞done.await(),等待全部N个线程任务跑完再收尾。
二、完整可运行代码示例
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DoubleLatchDemo {
// 并发线程数
private static final int THREAD_NUM = 100;
public static void main(String[] args) throws InterruptedException {
// 1. 启动门:计数器1,统一放行所有线程
CountDownLatch startLatch = new CountDownLatch(1);
// 2. 完成门:计数器等于线程总数,等待全部任务结束
CountDownLatch doneLatch = new CountDownLatch(THREAD_NUM);
ExecutorService pool = Executors.newFixedThreadPool(THREAD_NUM);
for (int i = 0; i < THREAD_NUM; i++) {
pool.submit(() -> {
try {
// 所有线程卡在这一步,等待统一信号
startLatch.await();
// ======================
// 业务逻辑:并发执行的代码
System.out.println("线程" + Thread.currentThread().getName() + "开始执行业务");
Thread.sleep(100);
// ======================
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 任务完成,完成门计数-1
doneLatch.countDown();
}
});
}
System.out.println("所有线程已就绪,准备统一并发执行...");
long start = System.currentTimeMillis();
// 主线程打开启动门,所有线程同时放行
startLatch.countDown();
// 主线程阻塞,等待全部100个线程执行完成
doneLatch.await();
long cost = System.currentTimeMillis() - start;
System.out.println("全部线程执行完毕,总耗时:" + cost + "ms");
pool.shutdown();
}
}
三、执行流程拆解
- 循环创建100个线程,每个线程执行
startLatch.await()阻塞; - 此时
startLatch计数=1,没人调用countDown,所有工作线程全部挂起; - 主线程执行
startLatch.countDown()→ startLatch计数变为0,所有阻塞线程同时唤醒,实现高并发; - 每个线程执行业务代码,走完后
doneLatch.countDown(),计数器逐步递减; - 主线程卡在
doneLatch.await(),直到doneLatch计数归0,主线程恢复执行,统计总耗时。
四、核心使用场景
- 接口/方法并发压测:模拟瞬时大量请求,精准测试并发下性能、锁竞争、数据库吞吐量;
- 多线程初始化同步:多个子模块等待统一信号并行初始化;
- 批量任务统一计时:消除线程启动时间差,得到真实并发耗时。
五、关键特性 & 对比单Latch
优点
- 精准控制并发时机,不会出现线程分批执行;
- 自动等待全部任务结束,无需手动维护线程集合+join;
- 代码简洁,无循环join繁琐逻辑。
与单Latch区别
只用doneLatch无法做到“同时并发”:线程创建有先后,会分批执行,并发冲击力弱;
双Latch通过启动门屏障,让所有线程卡在同一点,释放瞬间达到最大并发。
六、扩展变种:带超时控制
// 等待启动信号,最多5秒
startLatch.await(5, TimeUnit.SECONDS);
// 等待所有任务完成,最多30秒,避免死锁
doneLatch.await(30, TimeUnit.SECONDS);
七、常见问题
- 死锁风险
业务代码抛异常必须在finally执行done.countDown(),否则计数器永远不归0,主线程永久阻塞。 - 线程池数量匹配
线程池核心数 ≥ THREAD_NUM,否则线程池无空闲线程,无法全部阻塞在启动门。 - CountDownLatch不可复用
计数归零后无法重置,多次压测需要每次新建两个Latch;需要复用屏障用CyclicBarrier。
补充:CyclicBarrier 对比
- Double CountDownLatch:主线程控制开关,适合“统一发起并发,等待全部结束”压测场景;
- CyclicBarrier:线程之间互相等待,适合多线程分段同步,支持重置复用。
百流积聚,江河是也;文若化风,可以砾石。

浙公网安备 33010602011771号