双 CountDownLatch 经典并发压测模型(启动门+完成门)

一、模型原理

两个Latch分工明确:

  1. startLatch(启动门,初始计数=1)
    所有工作线程启动后统一阻塞在 start.await(),等待主线程统一开门,保证N个线程近乎同时并发执行,消除线程启动时差。
    主线程调用 start.countDown() 一次性放行所有线程。

  2. 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();
    }
}

三、执行流程拆解

  1. 循环创建100个线程,每个线程执行 startLatch.await() 阻塞;
  2. 此时 startLatch 计数=1,没人调用countDown,所有工作线程全部挂起;
  3. 主线程执行 startLatch.countDown() → startLatch计数变为0,所有阻塞线程同时唤醒,实现高并发;
  4. 每个线程执行业务代码,走完后 doneLatch.countDown(),计数器逐步递减;
  5. 主线程卡在 doneLatch.await(),直到doneLatch计数归0,主线程恢复执行,统计总耗时。

四、核心使用场景

  1. 接口/方法并发压测:模拟瞬时大量请求,精准测试并发下性能、锁竞争、数据库吞吐量;
  2. 多线程初始化同步:多个子模块等待统一信号并行初始化;
  3. 批量任务统一计时:消除线程启动时间差,得到真实并发耗时。

五、关键特性 & 对比单Latch

优点

  • 精准控制并发时机,不会出现线程分批执行;
  • 自动等待全部任务结束,无需手动维护线程集合+join;
  • 代码简洁,无循环join繁琐逻辑。

与单Latch区别

只用doneLatch无法做到“同时并发”:线程创建有先后,会分批执行,并发冲击力弱;
双Latch通过启动门屏障,让所有线程卡在同一点,释放瞬间达到最大并发。

六、扩展变种:带超时控制

// 等待启动信号,最多5秒
startLatch.await(5, TimeUnit.SECONDS);
// 等待所有任务完成,最多30秒,避免死锁
doneLatch.await(30, TimeUnit.SECONDS);

七、常见问题

  1. 死锁风险
    业务代码抛异常必须在finally执行done.countDown(),否则计数器永远不归0,主线程永久阻塞。
  2. 线程池数量匹配
    线程池核心数 ≥ THREAD_NUM,否则线程池无空闲线程,无法全部阻塞在启动门。
  3. CountDownLatch不可复用
    计数归零后无法重置,多次压测需要每次新建两个Latch;需要复用屏障用 CyclicBarrier

补充:CyclicBarrier 对比

  • Double CountDownLatch:主线程控制开关,适合“统一发起并发,等待全部结束”压测场景;
  • CyclicBarrier:线程之间互相等待,适合多线程分段同步,支持重置复用。
posted @ 2026-07-25 11:49  七星6609  阅读(4)  评论(0)    收藏  举报