countdownlatch通过await()阻塞主线程、各工作线程完成准备后调用countdown(),计数归零时统一唤醒实现同步发请求;需避免重复创建、异常未countdown、误用为可重用屏障,并注意httpclient线程安全与连接池配置。

CountDownLatch 怎么让线程等齐了再一起发请求
CountDownLatch 不是为“压测”设计的,但它能解决「所有线程准备好后同时发起请求」这个关键前提。核心是 countDown() 和 await() 的配合:主线程调用 await() 阻塞,每个工作线程执行完准备动作后调用 countDown();当计数归零,所有等待线程被唤醒——此时时间点基本一致。
容易踩的坑:
- 把
CountDownLatch放在循环里反复 new,导致每次 await 都没对应足够的 countDown,线程永远卡住 - 忘记在 try-catch 中调用
countDown(),异常抛出后计数不减,主线程死等 - 误用
CyclicBarrier场景(比如需要重复复位),但CountDownLatch是一次性门闩,不可重用
模拟并发请求时怎么避免 HttpClient 被复用污染
多个线程共用一个 CloseableHttpClient 实例是安全的,但若手动设置了 cookie、header 或连接池参数,就可能互相干扰。尤其压测中常需携带不同 token 或用户标识,必须在线程内独立构造或克隆上下文。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 不要全局单例
HttpClient后直接改 header——改用HttpUriRequestBase#setHeader()在每次请求对象上设 - 如果要用 cookie,别依赖
CookieStore全局实例,而是在每个线程里 new 一个BasicCookieStore并绑定到HttpClient实例 - 连接池要调大:
PoolingHttpClientConnectionManager的setMaxTotal(200)和setDefaultMaxPerRoute(50)得显式设置,否则默认只有 2 个连接,大量线程会排队阻塞
为什么用 CountDownLatch 做压测结果不准
CountDownLatch 只保证“开始时间趋近一致”,不控制请求发出后的执行节奏。真实压测中,你看到的 TPS 低、响应时间高,往往不是因为并发没起来,而是:
- JVM 预热不足:前几十次请求慢,影响平均值——应先预热再正式计时
- 没有排除 GC 毛刺:加
-XX:+PrintGCDetails看日志,频繁 Young GC 会让线程停顿 - 忽略了网络栈限制:Linux 默认
net.ipv4.ip_local_port_range是 32768–65535,最多约 3w 个临时端口,开 1w 线程+短连接会快速耗尽,报java.net.BindException: Address already in use
真正做压测,CountDownLatch 只适合写 demo 或小规模验证;上生产级压测得用 JMeter、wrk 或 Gatling。
一个可直接跑的最小并发请求示例(含异常防护)
以下代码片段聚焦「可靠触发 + 安全释放」,去掉业务逻辑也能看出结构重点:
int threadCount = 100;
CountDownLatch ready = new CountDownLatch(threadCount);
CountDownLatch start = new CountDownLatch(1);
CountDownLatch done = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
ready.countDown(); // 告诉主线程:我准备好了
start.await(); // 等统一发令
// ? 这里放你的 HTTP 请求逻辑
HttpResponse res = httpClient.execute(new HttpGet("http://localhost:8080/api/test"));
EntityUtils.consume(res.getEntity());
} catch (Exception e) {
// 记录错误,但必须确保 done 计数器减少
} finally {
done.countDown();
}
}).start();
}
ready.await(); // 等所有线程进入 await 状态
start.countDown(); // 发号施令
done.await(); // 等所有请求结束
注意 ready.await() 和 done.await() 之间才是真正的“压测窗口”,中间那段才是你要统计耗时、成功率的地方。漏掉这层区分,测出来的就是线程启动时间,不是接口性能。










