CountDownLatch 与 CyclicBarrier 区别深度解析

CountDownLatch 与 CyclicBarrier 深度解析:并发编程中的“闸门”与“集合点”

在 Java 并发工具包 java.util.concurrent 中,CountDownLatchCyclicBarrier 是最常用的两种线程同步辅助类。它们像是并发世界的两种“交通信号灯”:

  • 一个控制“何时开闸”,

  • 一个控制“所有人到齐再出发”。

虽然名字相似,但用途与机制大不相同。本文将带你从概念、源码、区别、应用场景到选型策略,全面理解这两个同步工具。


一、前置概念:线程同步的意义

在多线程环境中,各个线程常常需要在某些时刻协调执行顺序。例如:

  • 主线程需要等待多个子任务都完成;

  • 多个线程需要在每一轮阶段计算后对齐;

  • 某个操作需要所有线程同时开始。

传统做法是使用 wait()notify()synchronized 等低层机制,但编写复杂且容易出错。
CountDownLatchCyclicBarrier 就是为了解决这种阶段性协调问题的高级抽象。


二、CountDownLatch:一次性“闭锁闸门”

1. 原理简介

CountDownLatch 维护一个初始计数(count)。

  • 调用 countDown() 会让计数减一;

  • 当计数减到 0 时,所有在 await() 上等待的线程都会被释放。

它就像一道闸门

  • 计数没归零前,门是关闭的;

  • 当所有“任务”报告完成后,门一次性打开。

2. 常见用法

CountDownLatch latch = new CountDownLatch(3);

for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        try {
            // 执行子任务
            Thread.sleep(200);
            System.out.println(Thread.currentThread().getName() + " done");
        } catch (Exception ignored) {}
        latch.countDown(); // 任务完成后减一
    }).start();
}

// 主线程等待所有任务完成
latch.await();
System.out.println("All tasks finished, main thread proceeds");

输出类似:

Thread-0 done
Thread-1 done
Thread-2 done
All tasks finished, main thread proceeds

3. 特点

  • 一次性使用:计数到 0 后就不能重置;

  • 等待方不限于参与者:主线程、其他线程都可以 await()

  • 实现基于 AQS(AbstractQueuedSynchronizer)

  • 典型场景:主线程等待多个子任务结束


三、CyclicBarrier:可循环“集合点”

1. 原理简介

CyclicBarrier 用于让固定数量的线程在每一阶段都相互等待。
只有当所有线程都到达屏障点后,屏障才会放行,让它们同时进入下一阶段

之所以叫 Cyclic,是因为它可以自动复位,可多次使用。

2. 使用示例

int parties = 3;
CyclicBarrier barrier = new CyclicBarrier(parties, 
    () -> System.out.println("== 所有人到齐,开始下一阶段 =="));

for (int i = 0; i < parties; i++) {
    new Thread(() -> {
        try {
            stageWork(1);
            barrier.await(); // 阶段1集合

            stageWork(2);
            barrier.await(); // 阶段2集合

            stageWork(3);    // 最后一阶段
        } catch (Exception e) {
            e.printStackTrace();
        }
    }).start();
}

void stageWork(int stage) {
    System.out.println(Thread.currentThread().getName() + " 执行阶段 " + stage);
    Thread.sleep(200 + (int)(Math.random()*300));
}

输出(顺序可能不同):

Thread-1 执行阶段 1
Thread-2 执行阶段 1
Thread-0 执行阶段 1
== 所有人到齐,开始下一阶段 ==
Thread-2 执行阶段 2
Thread-0 执行阶段 2
Thread-1 执行阶段 2
== 所有人到齐,开始下一阶段 ==
Thread-1 执行阶段 3
Thread-2 执行阶段 3
Thread-0 执行阶段 3

每个阶段都要等全部线程到齐后,才能统一进入下一阶段。

3. 特点

  • 参与者线程数固定(由构造参数 parties 指定);

  • 到齐自动释放并复位,可多轮循环使用;

  • 可设置 barrier action(到齐时触发的回调逻辑);

  • 若某线程超时或中断,会导致屏障“破损”(BrokenBarrierException);

  • 实现基于 ReentrantLock + Condition


四、核心区别对比

项目 CountDownLatch CyclicBarrier
用途 等待若干事件完成(一次性) 多线程阶段同步(可重复)
谁在等 任意线程 参与的所有线程
是否可复用 否,计数归零后失效 是,自动复位
控制逻辑 计数递减到 0 开闸 所有线程到齐开闸
回调机制 可设置 barrier action
异常机制 await() 可被中断 可抛 BrokenBarrierException
实现基础 AQS ReentrantLock + Condition
常见场景 主线程等待子任务完成 分阶段并行任务、回合同步

口诀记忆:

Latch 等事件结束,一次开闸;Barrier 等人到齐,循环闸门。


五、应用场景举例

✅ CountDownLatch

  1. 主线程等待多个子任务完成

例如汇总多接口结果、并发加载数据源。

  1. 系统初始化依赖

启动时等多个组件加载完再对外服务。

  1. 并发压测

所有线程同时起跑(一个启动门)→ 同时统计完成(一个终点门)。

✅ CyclicBarrier

  1. 分阶段计算任务

如并行矩阵迭代,每轮计算后需等所有线程同步再进入下一轮。

  1. 分布式批量对齐

多个节点周期性计算指标,每一批计算完成后再统一写结果。

  1. 多人游戏/竞赛回合

每个回合所有玩家行动完才统一结算下一回合。

  1. 多阶段 ETL/清洗任务

各线程并行处理子表,每阶段完成一次汇总检查。


六、源码机制简析

CountDownLatch

内部持有 Sync extends AbstractQueuedSynchronizer

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
public void countDown() {
    sync.releaseShared(1);
}
protected int tryAcquireShared(int ignored) {
    return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int ignored) {
    int c, next;
    do { c = getState(); next = c-1; } while (!compareAndSetState(c, next));
    return next == 0;
}

状态值 state = 计数,归 0 时释放所有等待者。

CyclicBarrier

public int await() throws InterruptedException, BrokenBarrierException {
    lock.lock();
    try {
        int index = --count;
        if (index == 0) { // 最后一个到达
            barrierCommand.run(); // 可选回调
            nextGeneration();     // 重置 count
            trip.signalAll();     // 唤醒所有等待者
        } else {
            while (index > 0) trip.await();
        }
    } finally {
        lock.unlock();
    }
}

每轮结束自动进入下一代(generation),实现循环使用。


七、选型建议

需求 推荐
主线程等待多个任务完成 CountDownLatch
所有线程都要阶段对齐 CyclicBarrier
需要多阶段复用 CyclicBarrier
一次性同步(起跑/结束) CountDownLatch
参与者动态变化 Phaser(更灵活)

八、总结

  • CountDownLatch 像一次性的倒计时闸门:计数归零,一次开闸。

  • CyclicBarrier 像可重置的集合点:所有线程集合后一起出发,可多轮使用。

  • 二者都是“同步工具”,但并不保证共享数据的线程安全,只控制执行时机。

  • 使用时要注意线程超时、中断及异常破坏屏障的情况。

一句话收尾:

CountDownLatch 管“一次性等待”,CyclicBarrier 管“多阶段对齐”,理解它们的本质,就能优雅地写出同步且高效的并发程序。


posted on 2025-11-10 02:14  滚动的蛋  阅读(0)  评论(0)    收藏  举报

导航