CountDownLatch 在并发测试领域堪称经典利器,广泛应用于模拟真实的高并发压力场景,帮助开发人员精准计算接口的平均响应时间、吞吐量以及错误率等核心性能指标。它的核心目标在于攻克两个常见难题:第一,如何确保数十上百个线程真正实现“同时”发起请求,避免因线程调度的先后顺序造成启动时间偏差;第二,如何让主线程在所有请求处理完毕后统一汇总分析,防止由于数据不完整而得出错误的统计结论。

确保多线程真正并发启动
单纯通过 new Thread().start() 无法保证真正的“并发”——线程的创建与调度本身存在时序差异,先后启动的线程可能相差数毫秒甚至更长。CountDownLatch 在此处扮演“统一发令枪”的角色:
- 主线程首先创建一个
CountDownLatch startLatch = new CountDownLatch(1),充当统一的启动信号 - 每个工作线程启动后立即执行
startLatch.await(),使所有线程在同一个起跑线上等待 - 主线程完成所有线程的创建后,仅调用一次
startLatch.countDown(),瞬间唤醒所有等待的线程 - 通过这种方式,所有线程几乎在同一时间点(毫秒级)发起请求,真实模拟高并发压测场景
等待全部请求完成再统计结果
另一个关键问题是:主线程必须等待所有并发请求都返回之后,才能计算平均耗时、成功率等指标。此时需要第二个 CountDownLatch:
- 初始化
CountDownLatch doneLatch = new CountDownLatch(threadCount),用于跟踪所有线程的完成状态 - 每个工作线程在请求结束后(无论成功还是异常),必须执行
doneLatch.countDown()通知主线程 - 主线程调用
doneLatch.await(),推荐使用带超时参数的await(60, TimeUnit.SECONDS),防止个别请求卡死导致测试挂起 - 超时后主动中断尚未完成的线程,同时将超时数纳入统计,提升测试的鲁棒性和准确度
组合使用两个Latch实现完整压测流程
以下是一个典型的并发测试实现示例,展示了两个 CountDownLatch 的组合用法:
int threadCount = 100; CountDownLatch startLatch = new CountDownLatch(1); CountDownLatch doneLatch = new CountDownLatch(threadCount); Listdurations = Collections.synchronizedList(new ArrayList<>()); for (int i = 0; i < threadCount; i++) { new Thread(() -> { try { startLatch.await(); // 等待统一开始信号 long start = System.nanoTime(); // 执行实际业务请求,例如HTTP调用 doHttpRequest(); long end = System.nanoTime(); durations.add((end - start) / 1_000_000); // 毫秒 } catch (Exception e) { // 捕获异常,确保最终执行countDown以完成统计 } finally { doneLatch.countDown(); } }).start(); } // 所有线程就绪后,触发并发启动 startLatch.countDown(); // 主线程等待所有请求完成,设置60秒超时 doneLatch.await(60, TimeUnit.SECONDS); // 统计结果 double a vg = durations.stream().mapToLong(Long::longValue).a verage().orElse(0.0); System.out.println("平均响应时间:" + a vg + " ms");
注意避免常见陷阱
编写测试代码时,以下细节尤其容易出现问题:
- 每个线程必须严格只调用一次
countDown()—— 如果某个线程误调多次,会导致计数器提前归零,主线程过早退出,从而遗漏部分请求的统计数据 - 强烈建议在
finally块中调用countDown()—— 即使请求抛出异常或超时,也要确保计数器递减,否则await()将永久阻塞,测试进程被挂死 - 切勿复用已归零的 CountDownLatch 实例 —— 它是一次性同步工具,下次测试必须创建新实例;如需循环使用,建议改用
CyclicBarrier - 避免在主线程中同时使用
join()和await()—— 两者功能重叠且语义复杂,推荐统一使用 CountDownLatch 进行线程协调,代码逻辑更清晰
