CountDownLatch不能直接用于分布式压测齐发,因其基于JVM内存共享,跨机器时各实例独立;需配合Redis等外部协调机制统一触发各节点本地CountDownLatch释放。

CountDownLatch 本身不是分布式工具,它只在单 JVM 进程内有效,无法直接用于跨机器的“分布式压测并发齐发”。想用它实现分布式场景下的齐发,必须配合外部协调机制(比如 Redis、ZooKeeper 或中心化调度服务)来统一触发所有节点的倒计时释放。核心思路是:把 CountDownLatch 的 countDown() 和 await() 拆开——各节点本地等待,由一个协调者统一发令“开始”,再各自执行本地的并发压测逻辑。
为什么 CountDownLatch 不能直接用于分布式
CountDownLatch 是基于 AQS(AbstractQueuedSynchronizer)实现的内存同步工具,所有线程共享同一个实例对象,依赖 JVM 堆内存和锁状态。一旦压测节点分布在不同机器上(即多个 JVM),它们之间没有共享内存,各自的 CountDownLatch 实例完全独立,countDown() 在 A 机上调用,对 B 机上的实例毫无影响。
借助 Redis 实现分布式“齐发”的典型做法
用 Redis 的原子操作模拟“门闩”行为,让所有压测节点监听同一信号。常见组合是:Redis 的 SETNX + Pub/Sub 或更简单的 Redis String + 轮询/监听:
- 压测启动前,所有节点初始化本地 CountDownLatch(例如
new CountDownLatch(1)),并进入await()状态 - 控制台或调度服务执行
SET ready_flag 1 EX 60 NX(设置带过期的标志位) - 各节点通过定时轮询(如每 100ms 查一次
GET ready_flag)或订阅频道,检测到标志存在后立即调用countDown() - 本地 CountDownLatch 解锁,所有线程开始并发发起请求
代码片段示意(客户端侧)
每个压测节点运行以下逻辑(伪代码,以 Jedis 为例):
立即学习“Java免费学习笔记(深入)”;
CountDownLatch latch = new CountDownLatch(1);
// 启动压测线程组,全部卡在 await
ExecutorService pool = Executors.newFixedThreadPool(100);
for (int i = 0; i < 100; i++) {
pool.submit(() -> {
try {
latch.await(); // 齐发前阻塞
// 此处发起 HTTP 请求 / RPC 调用等压测动作
doRequest();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// 单独线程轮询 Redis 等待发令
new Thread(() -> {
Jedis jedis = new Jedis("redis-host");
while (true) {
String flag = jedis.get("ready_flag");
if ("1".equals(flag)) {
latch.countDown(); // 触发本地齐发
break;
}
try { Thread.sleep(50); } catch (InterruptedException e) { break; }
}
}).start();
更健壮的替代方案建议
生产级分布式压测推荐用专门协调服务,而非自己轮询 Redis:
- 使用 ZooKeeper 的 EPHEMERAL_SEQUENTIAL 节点 + Watcher:所有节点创建临时有序节点,当节点数达标且收到父节点数据变更通知时触发
-
接入 Nacos / Apollo 配置中心:监听某个开关配置项(如
stress.test.trigger=true),变更即触发本地 latch -
用消息队列(如 RocketMQ)广播“压测开始”事件:各节点订阅 Topic,收到消息后调用
countDown()
本质上,CountDownLatch 在这里只是“本地并发控制器”,真正解决分布式协同的是外部中间件。设计时要重点考虑超时机制、失败重试、节点异常退出后的清理,避免某台机器卡住导致整体阻塞。


















