Skip to content

CountDownLatch

一个很常见的场景:主线程要等待所有子任务完成后才能继续。

比如:游戏加载时,所有资源都加载完才能开始。

CountDownLatch 就是干这个的。

核心思想

想象一场跑步比赛:

发令枪响(主线程等待)

    ├── 选手1 ──► 准备完毕 ──► 等待
    ├── 选手2 ──► 准备完毕 ──► 等待
    └── 选手3 ──► 准备完毕 ──► 等待

    └── 所有选手准备完毕 ──► 比赛开始!

CountDownLatch 有一个计数器,初始值 = 等待的事件数。

每完成一个事件,countDown() 一次,计数器减 1。

计数器归零时,等待的线程被唤醒。

代码演示

基础用法

java
public class CountDownLatchDemo {

    public static void main(String[] args) throws InterruptedException {
        int taskCount = 3;
        CountDownLatch latch = new CountDownLatch(taskCount);

        System.out.println("主线程:开始执行");

        // 创建并启动多个子线程
        for (int i = 0; i < taskCount; i++) {
            final int taskId = i;
            new Thread(() -> {
                try {
                    System.out.println("子任务 " + taskId + " 开始执行");
                    Thread.sleep((long) (Math.random() * 2000 + 500));
                    System.out.println("子任务 " + taskId + " 执行完成");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown();  // 任务完成后计数减 1
                }
            }).start();
        }

        // 主线程等待所有子任务完成
        System.out.println("主线程:等待子任务完成...");
        latch.await();
        System.out.println("主线程:所有子任务已完成,继续执行");

        System.out.println("主线程:执行完成");
    }
}

带超时的等待

java
public class CountDownLatchWithTimeout {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);

        // 启动任务
        for (int i = 0; i < 3; i++) {
            final int taskId = i;
            new Thread(() -> {
                try {
                    Thread.sleep((long) (Math.random() * 3000 + 1000));
                    System.out.println("任务 " + taskId + " 完成");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown();
                }
            }).start();
        }

        // 等待最多 5 秒
        boolean completed = latch.await(5, TimeUnit.SECONDS);
        if (completed) {
            System.out.println("所有任务在 5 秒内完成");
        } else {
            System.out.println("等待超时,仍有任务未完成");
        }
    }
}

实际应用:模拟服务启动

java
public class ServiceStartupDemo {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("服务启动中...");

        CountDownLatch latch = new CountDownLatch(3);

        // 启动数据库连接池
        startComponent("数据库连接池", 2000, latch);

        // 启动缓存服务
        startComponent("缓存服务", 1500, latch);

        // 启动消息队列消费者
        startComponent("消息队列消费者", 1000, latch);

        // 等待所有组件启动完成
        latch.await();

        System.out.println("所有组件启动完成,服务已就绪");
    }

    private static void startComponent(String name, int initTime, CountDownLatch latch) {
        new Thread(() -> {
            try {
                System.out.println(name + " 正在启动...");
                Thread.sleep(initTime);
                System.out.println(name + " 启动完成");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                latch.countDown();
            }
        }, name).start();
    }
}

实际应用:并行计算

java
public class ParallelComputationDemo {

    public static void main(String[] args) throws InterruptedException {
        int[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        int threadCount = 3;
        int chunkSize = (int) Math.ceil((double) data.length / threadCount);
        long[] results = new long[threadCount];
        CountDownLatch latch = new CountDownLatch(threadCount);

        for (int i = 0; i < threadCount; i++) {
            final int threadId = i;
            final int start = i * chunkSize;
            final int end = Math.min(start + chunkSize, data.length);

            new Thread(() -> {
                try {
                    long sum = 0;
                    for (int j = start; j < end; j++) {
                        sum += data[j];
                    }
                    results[threadId] = sum;
                    System.out.println("线程 " + threadId + " 计算完成,和 = " + sum);
                } finally {
                    latch.countDown();
                }
            }).start();
        }

        latch.await();

        // 汇总结果
        long total = Arrays.stream(results).sum();
        System.out.println("最终结果:" + total);
    }
}

方法一览

方法说明
CountDownLatch(int count)构造方法,设置计数器初始值
await()等待计数器减到 0,可被中断
await(long timeout, TimeUnit)等待指定时间,返回是否成功
countDown()计数器减 1
getCount()获取当前计数器值

注意事项

  1. 一次性使用:CountDownLatch 创建后不能重置,计数减到 0 后不能再使用
  2. 必须在 finally 中调用:确保无论正常还是异常退出,都执行 countDown()
  3. 计数器初始值:必须大于 0,否则 await() 会立即返回
  4. 线程安全countDown()await() 都是线程安全的
java
// ❌ 错误示例:未在 finally 中调用 countDown
new Thread(() -> {
    doSomething();
    // 如果这里抛出异常,countDown 不会执行
    latch.countDown();
}).start();

// ✅ 正确示例
new Thread(() -> {
    try {
        doSomething();
    } finally {
        latch.countDown();  // 确保执行
    }
}).start();

要点回顾

CountDownLatch 适用于"一等多"的场景:

  • 主线程等待多个子任务完成
  • 服务启动等待多个组件就绪
  • 并行计算等待所有分片完成

记住:一次性,不可重用。

基于 VitePress 构建