“并发发令枪”是一种编程模式,指多个线程准备就绪后不抢先执行,统一等待信号,信号到达时毫秒级同步启动,满足零等待、一键激活、强一致性要求;其核心是基于countdownlatch实现的可重置组件。

什么是“并发发令枪”
它不是物理设备,而是一种编程模式:多个线程在准备好后,**不抢先执行**,统一等待一个信号;信号一到,全部线程**毫秒级同步启动**,无感知延迟。关键诉求是:零等待(无轮询/无休眠)、一键激活(单点触发)、强一致性(所有线程严格同时开始)。
为什么选 CountDownLatch 而非其他同步工具
CountDownLatch 天然适配“门栓”语义:初始化为 1,所有线程 await() 在同一道门前;调用 countDown() 相当于“开闸”,瞬间释放全部阻塞线程。相比 CyclicBarrier(需固定参与数、支持重用但更重)、Semaphore(许可语义偏松散)、Object.wait()(易出错、难管理),它最简洁、无锁、JVM 层级优化充分,且唤醒无调度抖动——真正实现“零等待”。
自定义组件设计要点
封装成可复用组件,避免每次手动 new CountDownLatch(1)。核心要解决三个问题:
- 线程安全的重复使用:每次发令后需重置状态,不能反复 new 对象(GC 压力+对象泄漏)
- 防止误触发与提前 await:未 start 就 await 应阻塞;已触发再 await 应立即通过(兼容异步注册场景)
- 支持超时与中断感知:生产环境必须可诊断、可退出,不能永久挂起
推荐结构:
<font color="#888">public class ConcurrentStartGun {
private final AtomicBoolean fired = new AtomicBoolean(false);
private final CountDownLatch latch = new CountDownLatch(1);
public void start() {
if (fired.compareAndSet(false, true)) {
latch.countDown(); // 仅首次生效
}
}
public void await() throws InterruptedException {
latch.await();
}
public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
return latch.await(timeout, unit);
}
public void reset() {
if (fired.get()) {
// 重置:新建 latch + 重置标志(注意:旧 latch 不可重用)
// 实际中建议用 volatile + double-check 或 ReentrantLock 保护
synchronized (this) {
if (fired.get()) {
fired.set(false);
// 注意:CountDownLatch 不可重置,必须新建
this.latch = new CountDownLatch(1);
}
}
}
}
}</font>
一键激活的实战用法
在 Spring Boot 或普通 Java 应用中,将 StartGun 注入为单例 Bean 或静态工具实例:
- 各工作线程启动时调用 gun.await() —— 立即阻塞,不消耗 CPU
- 主控逻辑(如 Controller 接口、定时任务、消息监听器)调用 gun.start() —— 全体线程瞬间解除阻塞,进入业务逻辑
- 若需多次使用,调用 gun.reset() 后再次 start,无需重建对象
示例:模拟 100 个请求并发压测入口
<font color="#888">StartGun gun = new StartGun();
for (int i = 0; i {
try {
gun.await(); // 零等待挂起
doActualWork(); // 所有线程在此刻精确对齐执行
} catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}).start();
}
// …… 准备就绪后
gun.start(); // 一触即发</font>
不复杂但容易忽略:CountDownLatch 本身不可重用,reset 本质是重建内部 latch 实例,务必保证多线程调用 reset 的安全性。真正做到了“构造一次,多次发令,毫秒齐发”。










