CyclicBarrier 超详细完整讲解

一、基础概述

java.util.concurrent.CyclicBarrier 中文:循环屏障 / 栅栏

核心思想

一组线程互相等待,所有线程都抵达屏障点之后,才统一继续往下执行; 屏障可以重复使用(Cyclic = 循环)。

形象理解: 一群人相约在门口集合,必须所有人到齐,才能一起出发; 出发完成后,屏障重置,可以进行下一轮集合。

和 CountDownLatch 最直观区别:

  • CountDownLatch:主线程等工人,门只能开一次
  • CyclicBarrier:工人互相等工人,门开完可以重置,多轮复用

二、构造方法

// n:参与等待的线程总数
public CyclicBarrier(int parties)

// parties:等待线程数量;barrierAction:所有人到达屏障后,优先执行的任务(由最后到达的线程执行)
public CyclicBarrier(int parties, Runnable barrierAction)

  

三、核心 API

1. int await() throws InterruptedException, BrokenBarrierException

线程到达屏障,阻塞等待其他线程。 返回值:当前线程抵达次序(0 ~ parties-1,最后到达的线程返回 0)

抛出异常:

  • InterruptedException:等待过程线程被中断
  • BrokenBarrierException:屏障破损(有线程中断、超时,屏障作废)

2. int await(long timeout, TimeUnit unit)

带超时等待: 超时仍没凑齐所有线程 → 抛出 TimeoutException,屏障会被标记为破损。

3. boolean isBroken()

判断屏障是否破损。 触发破损场景:

  1. 某个等待线程 await () 时被中断
  2. await 超时 一旦破损,其他正在等待的线程立刻收到 BrokenBarrierException

4. void reset()

手动重置屏障,开启新一轮等待。

如果此时还有线程正在等待,这些线程会抛出 BrokenBarrierException。

5. int getParties()

获取需要凑齐的总线程数

6. int getNumberWaiting()

当前正在屏障处阻塞等待的线程数量(瞬时快照)

四、简单示例(基础用法)

import java.util.concurrent.CyclicBarrier;

public class BarrierDemo {
    public static void main(String[] args) {
        // 等待3个线程全部到达屏障
        CyclicBarrier barrier = new CyclicBarrier(3, () -> {
            // 【屏障任务】所有线程到齐后,由最后到达的线程执行
            System.out.println("==== 所有人到齐,统一出发 ====");
        });

        for (int i = 1; i <= 3; i++) {
            int num = i;
            new Thread(() -> {
                try {
                    System.out.println("线程" + num + "到达屏障,等待其他人");
                    // 阻塞,等凑齐3个线程
                    barrier.await();
                    System.out.println("线程" + num + "继续执行后续任务");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

  

执行顺序特点: 三个线程全部打印「到达屏障」之后,才统一打印「继续执行」。

五、循环复用特性(关键区别 CountDownLatch)

CyclicBarrier barrier = new CyclicBarrier(2, () -> System.out.println("一轮完成"));

// 第一轮
new Thread(() -> {try{barrier.await();}catch(Exception e){}}).start();
new Thread(() -> {try{barrier.await();}catch(Exception e){}}).start();

// 短暂等待后,第二轮可以继续使用同一个barrier
Thread.sleep(1000);

new Thread(() -> {try{barrier.await();}catch(Exception e){}}).start();
new Thread(() -> {try{barrier.await();}catch(Exception e){}}).start();

  

CountDownLatch 计数器归零直接报废,无法第二轮等待

六、底层原理简述

CyclicBarrier 没有直接使用 AQS,底层依靠:

  • ReentrantLock + Condition 内部维护变量:
  1. parties:总等待线程数(固定不变)
  2. count:当前还未抵达屏障的线程数
  3. Generation(代) 每一轮成功放行后,新建一个 Generation,代表新一轮屏障; 如果出现中断 / 超时 → 当前 Generation 标记为 broken(破损)。

流程:

  1. 线程调用 await () → lock 加锁
  2. count 自减
  3. count > 0:condition.await () 阻塞
  4. count == 0:执行 barrierAction,唤醒所有等待线程,重置 count,新建 Generation,开启下一轮

七、典型业务场景

  1. 多阶段并行计算 多线程分块计算数据;第一阶段全部算完,统一汇总,再进入第二阶段计算。 例如:大数据分片处理 → 分片计算完成再合并,合并完再启动下一轮分片。
  2. 模拟多玩家同步游戏 所有玩家加载资源完毕后,才同时进入游戏场景。
  3. 批量任务分阶段执行 一批任务分成多个阶段,上一阶段所有线程完成,才能开启下一阶段。

八、CyclicBarrier vs CountDownLatch 深度对比

 

对比项CountDownLatchCyclicBarrier
核心角色 外部线程等待一组工作线程 一组线程之间互相等待
复用性 一次性,计数器归零失效 支持循环重复使用
计数器方向 只能递减,不可重置 每一轮凑齐后自动重置 count
回调任务 无内置回调 支持屏障回调(最后到达线程执行)
异常机制 不会 “连锁异常”,某线程异常不影响其他等待线程 某个线程中断 / 超时 → 屏障破损,其他等待线程抛出 BrokenBarrierException
底层 AQS 共享锁 ReentrantLock + Condition + Generation
await 含义 等待计数器到 0 等待所有同伴抵达屏障

一句话区分:

  • CountDownLatch:老板等员工干完活
  • CyclicBarrier:员工互相等待,所有人到齐再开工,干完可以再来一轮

九、高频坑点(生产避坑)

坑 1:线程数量不等于 parties

示例:barrier = new CyclicBarrier (3),但只启动 2 个线程调用 await () → 永久阻塞,程序卡死。

坑 2:屏障破损后不处理异常

只要其中一个线程 await 超时 / 被中断,屏障 broken = true; 其他正在 await 的线程立刻抛出 BrokenBarrierException。 解决方案:捕获异常后,可以手动 reset() 尝试重建屏障。

坑 3:混淆 barrierAction 执行线程

barrierAction 由最后调用 await () 的那条线程执行,不是主线程! 不要在里面放耗时阻塞任务,会拖慢整体放行。

坑 4:忘记 await 超时机制

生产环境一律推荐使用带超时 await(time, unit),防止死锁。

十、扩展:经典多阶段计算示例

public class MultiStageDemo {
    public static void main(String[] args) throws InterruptedException {
        int threadNum = 3;
        CyclicBarrier barrier = new CyclicBarrier(threadNum, () -> {
            System.out.println("==== 当前阶段全部执行完毕,进入下一阶段 ====");
        });

        for (int i = 1; i <= threadNum; i++) {
            int idx = i;
            new Thread(() -> {
                try {
                    // 阶段1
                    System.out.println("线程" + idx + ":执行阶段1");
                    Thread.sleep(500);
                    barrier.await();

                    // 阶段2
                    System.out.println("线程" + idx + ":执行阶段2");
                    Thread.sleep(500);
                    barrier.await();

                    // 阶段3
                    System.out.println("线程" + idx + ":执行阶段3");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

  

运行现象:严格分阶段推进,必须全部线程完成当前阶段,才能进入下一阶段

十一、三者速记(CountDownLatch / CyclicBarrier / Semaphore)

  1. CountDownLatch:一次性等待,外部线程等待一批任务完成
  2. CyclicBarrier:循环栅栏,线程互相等待,多阶段计算首选
  3. Semaphore:信号量,控制并发数量(限流、资源池)

 

微信图片_20260323111736_57_204

 

posted @ 2026-07-23 10:54  程序员食堂  阅读(11)  评论(0)    收藏  举报