Java 并发利器:CyclicBarrier 从入门到精通

【JUC】循环屏障CyclicBarrier详解 jdk中提供了许多的并发工具类,大家可能比较熟悉的有,主要用来阻塞一个线程运行,直到其他线程运行完毕。而jdk还有一个功能类似并发工具类,你知道它的作用吗?和有什么区别呢?对于CountDownLatch不了解的可以参考# CountDownLatch源码硬核解析,循环屏障,用来进行线程协作,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,会触发自己运行,运行完后,屏障又会开门,所有被屏障拦截的线程又可以继续运行。所以是可以重用的。 阅读详情

在 Java 多线程开发中,我们经常需要协调多个线程的执行。你有没有遇到过这样的场景:一个复杂任务被拆分成多个子任务并行执行,但所有子任务必须在某个点等待彼此完成后才能一起进入下一阶段。这时,CyclicBarrier 就能大显身手了!

CyclicBarrier 是什么?

CyclicBarrier(循环屏障)是 JDK 1.5 引入的 java.util.concurrent 包中的同步工具类,它的名字很形象 - "循环"意味着可以重复使用,"屏障"表示它能阻塞一组线程直到所有线程都到达某个点。

简单来说,CyclicBarrier 允许一组线程互相等待,直到所有线程都到达一个公共屏障点(barrier point),然后所有线程才会继续执行。更牛的是,它还支持在所有线程到达屏障时执行一个预定义的操作!

CyclicBarrier 与其他同步工具对比

CyclicBarrier vs CountDownLatch

小白常常搞混这两个工具,它们虽然都用于线程协调,但有明显区别:

在实际应用场景中:

  • CyclicBarrier 适合多阶段同步场景,如 MapReduce 中每个阶段结束都需要同步一次,或游戏中每局结束后准备下一局
  • CountDownLatch 适合单阶段等待场景,如主线程等待多个子线程完成初始化,或等待多个服务启动完成
  • CyclicBarrier 像是"人到齐才能开会",也就是所有参与者必须同时到达指定地点,缺一不可,且会议结束后下次还能再用同样的会议室开会
  • CountDownLatch 更像是"火箭发射倒计时",其中主线程调用await()等待,而多个子线程各自完成工作后调用countDown()减少计数,且倒计时结束后就不能重复使用

CyclicBarrier vs Phaser

Phaser 是 Java 7 引入的更灵活的同步工具,可以看作 CyclicBarrier 的增强版。下表展示了两者的核心差异:

特性

CyclicBarrier

Phaser

参与线程数

固定(构造时指定)

动态(可注册/注销线程)

阶段同步

每个阶段需固定线程数参与

支持动态调整阶段参与线程数

屏障动作

单一线程执行(最后到达者)

可由任意线程触发(通过arrive())

适用场景

固定线程数的多阶段同步

任务树分解、动态并行任务

选择时机:当任务规模可能变化或线程参与情况不固定时,考虑使用 Phaser;当架构简单且参与线程数固定时,CyclicBarrier 通常更加高效。

CyclicBarrier 的核心 API

先看看 CyclicBarrier 提供的主要方法:

// 构造方法,parties指定参与线程数
public CyclicBarrier(int parties)

// 构造方法,增加了屏障被打破时要执行的动作
public CyclicBarrier(int parties, Runnable barrierAction)

// 等待其他线程到达屏障点
public int await() throws InterruptedException, BrokenBarrierException

// 指定超时时间的等待
public int await(long timeout, TimeUnit unit)
    throws InterruptedException, BrokenBarrierException, TimeoutException

// 重置屏障到初始状态
public void reset()

// 查询当前在屏障处等待的线程数
public int getNumberWaiting()

// 查询此屏障是否处于损坏状态
public boolean isBroken()

实战案例:游戏大厅等待机制

想象一个多人游戏的匹配场景,只有当 4 名玩家都准备好后,游戏才能开始:

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ThreadLocalRandom;

public class GameLobbyDemo {

    private static final int PLAYER_COUNT = 4;

    public static void main(String[] args) {
        // 创建CyclicBarrier,当4个玩家都准备好时,执行开始游戏的逻辑
        CyclicBarrier barrier = new CyclicBarrier(PLAYER_COUNT, () -> {
            System.out.println("所有玩家已就绪,游戏开始!");
            System.out.println("====================");
        });

        // 模拟4个玩家加入游戏(每个玩家对应一个工作线程)
        for (int i = 1; i <= PLAYER_COUNT; i++) {
            final int playerId = i;
            new Thread(() -> {
                try {
                    // 模拟玩家加载资源,需要随机时间
                    System.out.println("玩家" + playerId + "正在加载游戏资源...");
                    int loadingTime = ThreadLocalRandom.current().nextInt(1000, 5000);
                    Thread.sleep(loadingTime);

                    System.out.println("玩家" + playerId + "已准备就绪,等待其他玩家... (耗时:" + loadingTime + "ms)");

                    // 在屏障点等待其他玩家
                    barrier.await();

                    // 所有玩家都准备好后,开始游戏
                    System.out.println("玩家" + playerId + "进入游戏世界");

                    // 游戏结束后,可以重用CyclicBarrier进行下一局
                    System.out.println("玩家" + playerId + "本局游戏结束,准备下一局");

                    // 模拟短暂休息
                    Thread.sleep(ThreadLocalRandom.current().nextInt(500, 1500));

                    // 重新等待其他玩家准备下一局
                    System.out.println("玩家" + playerId + "已准备好下一局,等待其他玩家...");
                    barrier.await();

                    System.out.println("玩家" + playerId + "开始新一局游戏");

                } catch (InterruptedException | BrokenBarrierException e) {
                    System.err.println("玩家" + playerId + "遇到问题,退出游戏");
                    e.printStackTrace(); // 打印详细异常堆栈,方便排查问题
                }
            }, "游戏线程-" + i).start();
        }
    }
}

运行结果会显示玩家以不同速度准备就绪,然后在屏障点等待其他玩家,所有玩家都准备好后才会一起开始游戏。更重要的是,游戏结束后,我们可以重用同一个 CyclicBarrier 开始新一局!

深入案例:分阶段并行计算

除了游戏大厅等待机制,CyclicBarrier 还可以应用于分阶段并行计算的场景。假设我们要对一个大数组进行多阶段处理,每个阶段都需要等待所有工作线程完成当前阶段才能进入下一阶段:

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.Arrays;

public class ParallelArrayProcessor {
    private static final int THREAD_COUNT = 3;
    private static final int ARRAY_SIZE = 10; // 故意设置为不能被线程数整除
    private static int[] dataArray = new int[ARRAY_SIZE];

    // 预期的阶段性结果,用于验证处理正确性
    private static final int[] EXPECTED_STAGE1 = new int[ARRAY_SIZE]; // 全部为2
    private static final int[] EXPECTED_STAGE2 = new int[ARRAY_SIZE]; // 全部为5
    private static final int[] EXPECTED_STAGE3 = new int[ARRAY_SIZE]; // 全部为2

    static {
        // 初始化预期结果数组
        Arrays.fill(EXPECTED_STAGE1, 2);  // 1*2=2
        Arrays.fill(EXPECTED_STAGE2, 5);  // 2+3=5
        Arrays.fill(EXPECTED_STAGE3, 2);  // 5/2=2 (整数除法)
    }

    public static void main(String[] args) {
        // 初始化数组
        Arrays.fill(dataArray, 1);
        System.out.println("初始数组: " + Arrays.toString(dataArray));

        // 创建屏障,所有线程到达后验证结果并打印当前状态
        CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT, () -> {
            System.out.println("==当前阶段完成,数组状态: " + Arrays.toString(dataArray) + "==");

            // 验证阶段结果
            if (Arrays.equals(dataArray, EXPECTED_STAGE1)) {
                System.out.println("√ 第一阶段结果验证成功");
            } else if (Arrays.equals(dataArray, EXPECTED_STAGE2)) {
                System.out.println("√ 第二阶段结果验证成功");
            } else if (Arrays.equals(dataArray, EXPECTED_STAGE3)) {
                System.out.println("√ 第三阶段结果验证成功");
            }

            // 注意:这里的验证代码是轻量级的,在实际应用中,
            // 屏障动作应保持简短,避免阻塞其他线程
        });

        // 启动工作线程
        for (int t = 0; t < THREAD_COUNT; t++) {
            final int threadNum = t;
            new Thread(() -> {
                try {
                    // 计算每个线程负责处理的数组部分
                    int perThreadBaseSize = ARRAY_SIZE / THREAD_COUNT; // 每个线程基本处理元素数
                    int extraElements = ARRAY_SIZE % THREAD_COUNT;     // 剩余需分配的额外元素

                    // 计算起始索引,前extraElements个线程各多处理1个元素
                    int start = threadNum * perThreadBaseSize + Math.min(threadNum, extraElements);

                    // 计算结束索引
                    int end;
                    if (threadNum < extraElements) {
                        // 前extraElements个线程多处理一个元素
                        end = start + perThreadBaseSize + 1;
                    } else {
                        end = start + perThreadBaseSize;
                    }

                    // 第一阶段:每个元素乘以2
                    // 这个阶段模拟数据的初步放大处理
                    for (int i = start; i < end; i++) {
                        dataArray[i] *= 2; // 数据放大,值变为2
                        Thread.sleep(100); // 模拟计算耗时
                    }
                    System.out.println("线程" + threadNum + "完成第一阶段" +
                                      ",处理范围[" + start + "," + end + ")");

                    // 等待所有线程完成第一阶段
                    barrier.await();

                    // 第二阶段:每个元素加3
                    // 这个阶段模拟数据的偏移处理
                    for (int i = start; i < end; i++) {
                        dataArray[i] += 3; // 数据偏移,值变为5
                        Thread.sleep(100); // 模拟计算耗时
                    }
                    System.out.println("线程" + threadNum + "完成第二阶段" +
                                      ",处理范围[" + start + "," + end + ")");

                    // 等待所有线程完成第二阶段
                    barrier.await();

                    // 第三阶段:每个元素除以2
                    // 这个阶段模拟数据的归一化处理
                    for (int i = start; i < end; i++) {
                        dataArray[i] /= 2; // 数据归一化,值变为2.5,因为是int所以结果是2
                        Thread.sleep(100); // 模拟计算耗时
                    }
                    System.out.println("线程" + threadNum + "完成第三阶段" +
                                      ",处理范围[" + start + "," + end + ")");

                    // 等待所有线程完成第三阶段
                    barrier.await();

                } catch (InterruptedException | BrokenBarrierException e) {
                    System.err.println("线程" + threadNum + "处理出错:" + e.getMessage());
                    e.printStackTrace();
                }
            }, "计算线程-" + t).start();
        }
    }
}

这个例子展示了如何使用 CyclicBarrier 来协调多个线程对同一数据结构的分阶段处理。每个线程负责处理数组的一部分,在每个阶段结束时,所有线程都会在屏障点等待,确保所有部分都处理完毕后再一起进入下一阶段。特别注意的是,当数组大小不能被线程数整除时,我们采用了"前几个线程多处理一个元素"的分配策略。

实际案例:模拟银行柜台服务

再看一个更贴近实际的例子,模拟银行服务流程,客户需要依次经过填表、审核、办理、存档四个环节:

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ThreadLocalRandom;

public class BankServiceSimulation {
    private static final int CUSTOMER_COUNT = 5;  // 客户数量
    private static final int STEP_COUNT = 4;      // 服务步骤数量

    private static final String[] STEPS = {
        "填写表格", "材料审核", "业务办理", "资料存档"
    };

    // 将客户处理每个步骤的逻辑提取成一个单独的方法,提高代码复用性
    private static void processStep(int customerId, int step, CyclicBarrier barrier)
            throws InterruptedException, BrokenBarrierException {
        // 随机处理时间,模拟不同客户在不同环节的耗时差异
        int processingTime = ThreadLocalRandom.current().nextInt(500, 2000);
        Thread.sleep(processingTime);

        System.out.println("客户" + customerId + "完成" + STEPS[step] +
                          ",用时" + processingTime + "ms");

        // 正确计算已到达和剩余线程数
        int arrived = CUSTOMER_COUNT - barrier.getNumberWaiting(); // 已到达线程数
        int remaining = barrier.getNumberWaiting(); // 剩余未到达线程数

        System.out.println("客户" + customerId + "等待其他人,当前已有" + arrived +
                          "人完成,还有" + remaining + "人未完成" + STEPS[step]);

        // 等待所有客户完成当前环节
        barrier.await();
    }

    public static void main(String[] args) {
        // 为每个步骤创建一个CyclicBarrier
        CyclicBarrier[] barriers = new CyclicBarrier[STEP_COUNT];

        for (int i = 0; i < STEP_COUNT; i++) {
            final int stepIndex = i;
            barriers[i] = new CyclicBarrier(CUSTOMER_COUNT, () -> {
                System.out.println("\n===== 所有客户已完成环节:" + STEPS[stepIndex] + " =====\n");

                // 模拟工作人员准备下一环节
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    System.err.println("工作人员准备被中断:" + e.getMessage());
                }

                if (stepIndex < STEP_COUNT - 1) {
                    System.out.println("工作人员准备开始下一环节:" + STEPS[stepIndex + 1]);
                } else {
                    System.out.println("银行工作日结束!");
                }
            });
        }

        // 创建客户线程,每个客户由一个独立工作线程表示
        for (int i = 1; i <= CUSTOMER_COUNT; i++) {
            final int customerId = i;
            new Thread(() -> {
                try {
                    // 使用提取的方法处理每个步骤
                    for (int step = 0; step < STEP_COUNT; step++) {
                        processStep(customerId, step, barriers[step]);
                    }

                    System.out.println("客户" + customerId + "办完所有业务,满意离开");

                } catch (InterruptedException e) {
                    System.err.println("客户" + customerId + "被叫去做别的事情,中断办理:" + e.getMessage());
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    System.err.println("客户" + customerId + "遇到系统问题,无法继续办理:" + e.getMessage());
                    e.printStackTrace();
                }
            }, "银行线程-" + i).start();
        }
    }
}

这个例子展示了如何通过代码模块化提高可读性和可维护性,同时更能体现 CyclicBarrier 在多阶段同步场景中的应用,每个阶段都需要所有线程到达后才能共同进入下一阶段。

真实应用场景

CyclicBarrier 在实际项目中有许多应用,以下是几个真实技术栈中的应用案例:

Spring Batch 中的并行处理

在 Spring Batch 框架中,当需要并行处理数据切片时,可以使用 CyclicBarrier 确保所有工作线程完成当前批次处理后再进行批次提交:

// 伪代码示例
public class ParallelItemProcessor implements ItemProcessor<InputData, OutputData> {
    private final CyclicBarrier barrier;
    private final List<Worker> workers;

    @Override
    public OutputData process(InputData item) throws Exception {
        // 分派任务给工作线程
        distributeToWorkers(item);

        // 等待所有工作线程完成处理
        barrier.await();

        // 合并结果
        return mergeResults();
    }
}

Kafka 消费者的批处理同步

在 Kafka 消费者组实现中,当多个线程并行处理消息时,使用 CyclicBarrier 可以确保所有消息处理完成后再提交偏移量:

// 伪代码示例
public class BatchKafkaConsumer {
    private final CyclicBarrier processBarrier;

    public void consumeBatch(List<ConsumerRecord<K, V>> records) {
        // 将记录分配给多个工作线程处理
        for (int i = 0; i < threadCount; i++) {
            final int threadId = i;
            executor.submit(() -> {
                try {
                    processRecordsSlice(recordSlices.get(threadId));
                    // 等待所有线程完成处理
                    processBarrier.await();
                } catch (Exception e) {
                    // 处理异常
                }
            });
        }

        // 主线程也等待,确保在提交偏移量前所有处理都完成
        processBarrier.await();
        consumer.commitSync();
    }
}

性能测试中的"同步发车"

在性能测试中,我们常需要模拟大量用户同时发起请求,CyclicBarrier 正是实现这种"同步发车"的理想工具:

public class LoadTester {
    public void runTest(int threadCount, int requestsPerThread) {
        CyclicBarrier startBarrier = new CyclicBarrier(threadCount + 1); // +1 包括主线程
        CountDownLatch finishLatch = new CountDownLatch(threadCount);

        // 创建并启动测试线程
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                try {
                    // 等待所有线程就绪
                    startBarrier.await();

                    // 执行测试请求
                    for (int j = 0; j < requestsPerThread; j++) {
                        executeRequest();
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    finishLatch.countDown();
                }
            }).start();
        }

        try {
            // 主线程解除屏障,所有测试线程同时开始
            startBarrier.await();
            System.out.println("所有测试线程同时启动!");

            // 等待所有测试线程完成
            finishLatch.await();
            System.out.println("测试完成!");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

常见问题及解决方案

问题 1:CyclicBarrier.await()抛出 BrokenBarrierException

问题分析: 当 CyclicBarrier 处于"损坏"状态时,调用 await()会抛出 BrokenBarrierException。这可能发生在:

  • 某个等待的线程被中断
  • 调用 reset()方法重置屏障
  • 屏障动作执行时抛出异常

解决方案:

try {
    barrier.await();
} catch (BrokenBarrierException e) {
    if (barrier.isBroken()) {
        System.err.println("屏障已损坏,尝试恢复,原因:" + e.getMessage());
        // 记录详细日志,便于问题排查
        e.printStackTrace();

        // 根据应用场景决定重置屏障
        barrier.reset();

        // 根据业务逻辑决定是重试还是放弃
        boolean shouldRetry = determineIfShouldRetry();
        if (shouldRetry) {
            try {
                barrier.await();
            } catch (Exception ex) {
                // 重试失败处理
                System.err.println("重试失败,放弃本次操作");
            }
        }
    }
}

问题 2:线程永久等待

问题分析: 如果参与线程数量设置错误,或某些线程未能达到屏障点(比如由于异常退出),其他线程可能永久等待。这是多线程开发中常见的死锁问题之一。

解决方案:

try {
    // 最多等待10秒,防止无限等待
    boolean result = barrier.await(10, TimeUnit.SECONDS) >= 0;
    if (result) {
        System.out.println("所有线程都已到达屏障点,继续执行");
    } else {
        System.err.println("等待超时,可能有线程未到达或出现异常");
    }
} catch (TimeoutException e) {
    System.err.println("等待超时,执行超时处理逻辑:" + e.getMessage());
    // 记录超时线程信息,方便排查问题
    Thread currentThread = Thread.currentThread();
    System.err.println("超时线程:" + currentThread.getName() + ", 状态:" + currentThread.getState());
    // 执行超时处理逻辑,比如释放资源、记录日志等
    handleTimeout();
}

问题 3:性能和死锁问题

问题分析: 不当使用 CyclicBarrier 可能导致性能瓶颈或死锁。主要原因包括:

  • 屏障动作执行时间过长
  • 屏障内部循环依赖
  • 参与线程数设置不当

解决方案:

  • 避免在屏障动作中执行耗时操作,将耗时操作异步化
  • 不要在 barrierAction 中调用同一个 CyclicBarrier 的 await()方法,避免死锁
  • 合理设置参与线程数量,确保与实际等待线程数匹配
  • 考虑使用线程池管理线程,避免线程数量失控
// 错误示例:在屏障动作中调用自身await(),导致死锁
CyclicBarrier deadlockBarrier = new CyclicBarrier(2, () -> {
    try {
        // 在屏障动作中又调用await(),会导致死锁
        deadlockBarrier.await();
    } catch (Exception e) {
        e.printStackTrace();
    }
});

// 推荐方式:屏障动作简短,耗时操作异步处理
CyclicBarrier goodBarrier = new CyclicBarrier(2, () -> {
    // 简短的操作,不调用await()
    System.out.println("所有线程到达屏障点,执行简单的汇总操作");
    // 如果有耗时操作,放在单独的线程中异步执行
    new Thread(() -> {
        try {
            performTimeConsumingTask();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }).start();
});

CyclicBarrier 实现原理简化分析

CyclicBarrier 的工作流程可以分解为以下步骤:

CyclicBarrier 内部主要依赖 ReentrantLock 和 Condition 实现:

以下是 CyclicBarrier 的简化实现原理示例代码:

// 这是一个简化的CyclicBarrier实现原理示例,不是实际源码
public class SimplifiedCyclicBarrier {
    // 重入锁,用于线程同步
    // 选择ReentrantLock而非synchronized的原因:
    // 1. 提供更灵活的锁操作,如可中断锁、超时锁、公平锁
    // 2. 支持多个条件变量(Condition),实现精准线程唤醒
    // 3. 相比synchronized+wait/notify的复杂控制逻辑,代码更清晰
    private final ReentrantLock lock = new ReentrantLock();

    // 条件变量,用于线程等待和唤醒,支持更精细的线程控制
    private final Condition trip = lock.newCondition();

    // 参与线程数
    private final int parties;

    // 当前等待的线程数
    private int count;

    // 所有线程到达屏障时执行的动作
    // 由最后一个到达的线程执行,天然线程安全,无需额外同步
    private final Runnable barrierCommand;

    // 表示当前屏障的"代"
    // 在JDK实现中,这个引用是volatile的,确保多线程环境下的内存可见性
    // volatile保证所有线程看到的generation引用都是最新的,避免脏读问题
    private Generation generation = new Generation();

    // 私有静态内部类,表示屏障的一代
    // 用Generation对象而不是简单的boolean可以避免虚假唤醒和ABA问题
    private static class Generation {
        // 标记此代屏障是否被破坏
        boolean broken = false;
    }

    public SimplifiedCyclicBarrier(int parties, Runnable barrierAction) {
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }

    // 等待其他线程到达屏障点
    public int await() throws InterruptedException, BrokenBarrierException {
        // 获取锁
        lock.lock();
        try {
            // 获取当前代,用于标识这一轮的屏障
            final Generation g = generation;

            // 如果当前代已被破坏,抛出异常
            if (g.broken) {
                throw new BrokenBarrierException();
            }

            // 如果线程被中断,破坏当前代并抛出异常
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }

            // 剩余等待线程数减1
            int index = --count;

            // 如果是最后一个到达的线程
            if (index == 0) {
                boolean ranAction = false;
                try {
                    // 执行屏障动作
                    if (barrierCommand != null)
                        barrierCommand.run();
                    ranAction = true;

                    // 重置屏障,开始新一代
                    nextGeneration();
                    return 0;
                } finally {
                    // 如果执行barrierCommand失败,破坏屏障
                    if (!ranAction)
                        breakBarrier();
                }
            }

            // 如果不是最后一个到达的线程,则等待
            for (;;) {
                try {
                    // 在条件变量上等待
                    // 此处可能发生"虚假唤醒",即线程可能在没有被明确通知的情况下从await()返回
                    trip.await();
                } catch (InterruptedException ie) {
                    // 如果等待被中断,检查状态
                    if (g == generation && !g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                        // 如果屏障已经重置或被破坏,只是重新中断当前线程
                        Thread.currentThread().interrupt();
                    }
                }

                // 检查屏障是否被破坏
                if (g.broken)
                    throw new BrokenBarrierException();

                // 通过对比generation引用,确保唤醒的线程属于当前有效屏障周期
                // 避免"虚假唤醒"导致的问题,只有同一generation内的唤醒信号才有效
                if (g != generation)
                    return index;
            }
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    // 破坏屏障,唤醒所有等待的线程
    private void breakBarrier() {
        generation.broken = true;
        count = parties;
        trip.signalAll();
    }

    // 重置屏障,开始新一代
    private void nextGeneration() {
        // 唤醒此代所有等待的线程
        trip.signalAll();
        // 重置count
        count = parties;
        // 创建新一代,这一步非常关键
        // 新的Generation引用确保await()方法中的代检查可以区分不同轮次
        generation = new Generation();
    }
}

这个简化的实现展示了 CyclicBarrier 的核心工作原理:

  1. 使用 ReentrantLock 而非 synchronized,提供更灵活的锁操作和精准的条件控制
  2. 初始化时,count 值设为参与线程数(parties)
  3. 每个线程调用 await()时,count 减 1
  4. 如果 count 不为 0,当前线程在 Condition 上等待
  5. 如果 count 为 0,执行屏障动作,重置 count,并唤醒所有等待线程
  6. Generation 对象用于标识不同的屏障周期,每次重置都创建新 Generation,通过引用比较避免虚假唤醒

使用场景总结

CyclicBarrier 非常适合以下场景:

  1. 并行迭代算法:将大任务拆分成多个子任务,每轮迭代结束后同步一次
  2. 多阶段并行计算:计算分为多个阶段,每个阶段都需要使用上一阶段的结果
  3. 游戏同步:等待所有玩家加载完成后开始游戏
  4. 分布式系统中的数据一致性:等待所有节点数据同步后进行下一步操作
  5. 模拟测试:控制多个线程同时发起请求,测试系统性能

实用技巧和常见问题解答

实用技巧

  1. 屏障动作保持轻量:屏障动作由最后一个到达的线程执行,耗时操作应异步处理
  2. 总是使用带超时的 await():避免由于线程异常导致的永久等待
  3. 重置前确保线程已释放:不要在仍有线程在等待时调用 reset()

常见面试问题解答

Q: CyclicBarrier 如何实现可重用?
A: CyclicBarrier 通过在最后一个线程到达时创建新的 Generation 对象,并重置计数器来实现可重用。这使得它能在一轮同步完成后自动准备下一轮同步,无需手动重置。

Q: 屏障损坏后如何恢复?
A: 调用reset()方法可以恢复损坏的屏障。这会创建一个新的 Generation 对象,重置计数器,并唤醒所有等待的线程。但注意,等待中的线程会收到 BrokenBarrierException。

Q: 为什么 CyclicBarrier 构造函数需要指定参与线程数?
A: 指定参与线程数是为了让 CyclicBarrier 知道需要等待多少个线程到达。这个数字必须精确,否则可能导致永久等待(如果实际线程数少于指定数)或提前释放(如果还有线程未到达就已触发)。

总结

特性

说明

核心功能

允许多个线程互相等待,直到所有线程都到达某个点

重用性

可以被重置并重复使用,适合迭代或周期性任务

屏障动作

支持在所有线程到达屏障时执行预定义的操作

线程安全

屏障动作在单线程中执行,天然线程安全

异常处理

通过 BrokenBarrierException 和 isBroken()方法处理异常情况

使用场景

多阶段计算、并行算法、游戏同步、分布式系统协调等

性能考虑

需注意 parties 设置、屏障动作耗时、异常处理等问题

CyclicBarrier 是 Java 并发工具包中一个非常实用的同步工具,掌握它能让你在多线程协作场景中事半功倍。它不仅能确保多个线程步调一致地执行,还能通过屏障动作在同步点执行额外的处理逻辑。

Java并发编程利器CyclicBarrier:从使用到源码深度解析,掌握多线程同步的艺术 理解可重用性设计:通过Generation实现屏障重置。重视异常处理:避免屏障破坏导致程序异常。结合业务场景:选择CyclicBarrier或Phaser等更高级工具。 阅读详情

相关推荐

【高并发】JUC中的循环栅栏CyclicBarrier的6种使用场景

JUC中的循环栅栏CyclicBarrier的6种使用场景。 CyclicBarrier通常称为循环屏障。它和CountDownLatch很相似,都可以使线程先等待然后再执行。不过CountDownLatch是使一批线程等待另一批线程执行完后再执行;而CyclicBarrier只是使等待的线程达到一定数目后再让它们继续执行。故而CyclicBarrier内部也有一个计数器,计数器的初始值在创建对象时通过构造参数指定,如下所示: 。除此之外,CyclicB

九师兄 6574

CyclicBarrier 使用案例详解

CyclicBarrier 概念: CyclicBarrier 字面意思是环栅栏,是 JUC 下的一个并发工具,跟 CountDownLatch 很相似,都可以使线程先等待然后再执行,但是它的功能比 CountDownLatch 更加复杂和强大, CountDownLatch 是一个或者多个线程等待另外一批线程执行完毕后,在接着执行,而 CyclicBarrier 是等待一批线程到达某个状态之后再同时开始执行,回环的意思是当所有的线程被释放后,CyclicBarrier 可以被重启,也就是可以重复使用

weixin_42118323的博客 4586

Java并发利器:CyclicBarrier的使用与实例

CyclicBarrier是Java并发工具包中一个非常强大的类,它允许一组线程在某个屏障点相互等待,直到所有线程都到达该点后才继续执行。而且,CyclicBarrier是可重用的,这意味着一旦所有线程通过屏障点后,计数器会自动重置为初始值,线程可以再次使用它。当进度条的值达到某个特定值时,线程会在屏障点等待,直到所有线程都到达该点后才继续执行。这种机制非常适合需要多个线程在某个点同步的场景,例如在多线程任务中,每个线程完成一部分工作后需要等待其他线程完成,然后一起进入下一个阶段。

2501_90323865的博客 858

简述CyclicBarrier、CountDownLatch和Semaphore

文章目录前言一. 概念篇1.1 CyclicBarrier1.2. CountDownLatch1.3. Semaphore二. 案例篇2.1. CyclicBarrier2.2. CountDownLatch2.3. Semaphore三. 深入篇3.1. CyclicBarrier3.2. CountDownLatch3.3. Semaphore3.4. CAS3.5. AQS 前言

米二的博客 3759

紫光同创FPGA中ADF网表文件的黑匣子封装与PDS环境实战指南

本文详细介绍了在紫光同创FPGA开发环境中使用ADF网表文件进行黑匣子封装的实战指南。通过PDS环境配置、加密ADF生成、黑匣子集成及验证调试等步骤,帮助开发者实现核心IP保护,同时保持设计性能。特别适用于需要模块级代码保护的企业级FPGA开发场景。

weixin_33725239的博客 341

Rapid SCADA MQTT

一、配置RapidSCADA 1、准备KpMqtt.dll、Jint.dll库文件,从网盘下载。 2、将以上两个文件拷贝到SCADA安装目录的Comm/KP目录下,如下: C:\SCADA\ScadaComm\KP 3、新建SCADA项目 4、新建Line 5、新建Device 6、拷贝Config目录下的KpMqtt_001.xml文件、KpMqtt_Job.js文件到项目Comm\Config下,如下示例链接 C:\SCADA\Projects\MQTTProject\Instances\Defau

mumanquan1的博客 3007

CyclicBarrier 用法详解

CyclicBarrier 用法详解 CyclicBarrier使用场景 用于协调多个线程同步执行操作的场合,所有线程等待完成,然后一起做事情( 相互之间都准备好,然后一起做事情 ) 例如百米赛跑,必须等待所有运动员都准备好了,才能比赛。 运动员准备好,裁判也准备好,才能开始正式比赛。 CyclicBarrier cyclicBarrier = new CyclicBarrier(3); new Thread(() -> { try {

. 8953

用AI画电路板AI EDA初探Creating WiFi Radio with CELUS: A Step-by-Step Tutorial 使用CELU.IO创建WiFi收音机:分步教程

结论:整体还不太成熟,所选器件单元是欧美思维,器件选型成本和咱们华强北还是无法比。可以关注迭代,看客就当走马观花就行了;不过里面的单元模块还带有AI提问的这个比较好,值得国内EDA从业者学习。话不多说上正文:本教程目的是让我们深入了解如何使用CELUS设计平台构建WiFi收音机实战项目。本指南的重点是使用该平台来选择正确的解决方案,并整合一个项目,让您可以将音乐和节目从互联网直接传输到您自己构建的扬声器。

2301_81235726的博客 1万+

Java并发编程利器:CyclicBarrier与CountDownLatch解析

在Java的并发编程领域,Java并发包(java.util.concurrent)为开发者提供了丰富的同步工具类,旨在有效解决多线程协作中的各类复杂问题。这些工具类不仅增强了程序对并发场景的处理能力,还显著提升了代码的可读性和可维护性。本文将深入探讨两种尤为常用的同步辅助类:CyclicBarrier与CountDownLatch。

Rye的博客 2417

通讯接口应用笔记3:使用W5500实现Modbus TCP服务器

  前面我们设计实现了W5500的驱动程序,也讲解了驱动的使用方式。在最近一次的项目应用中,正好有一个使用W5500实现TCP通讯的需求,所以我们就使用该驱动程序轻松实现。这一篇中我们就来说一说基于我们W5500通讯驱动程序实现TCP通讯的过程。 1、应用需求   在本次应用中,要求实现一个基于W5500的Modbus TCP服务器。这个需求的描述虽然只有一句话,但是这个需求的内容可不简单。我们首先来分析一下这个需求的具体内容。   为了实现基于W5500的Modbus TCP服务器,我们必先须基于W550

木南创智 5499

【PADS-开发指南】

馒头破戒大师

sinat_23896491的博客 3224

Java并发利器:CyclicBarrier深度解析

它是一个可重复使用的“集合点”:N 个线程各自完成任务后在此汇合,全部到齐才一起继续;支持在汇合时执行一个汇总操作,并且任何一人掉队(中断/超时)则全员失败。用Generation隔离不同轮次;用实现高效等待/唤醒;提供强一致性语义和灵活的扩展点(barrier action)。这也是 Doug Lea 并发设计的经典之作。

m0_37607945的博客 858

HarmonyOS应用开发者基础认证

B. 自定义组件:可复用的 UI 单元,可组合其它组件。D. 内置组件:ArkTS中默认内置的基本组件和布局组件,开发者可以直接调用,如Column、Text、Divider、Button等。E. 属性方法:用于组件属性的配置,统一通过属性方法进行设置,如fontSize()、width()、height()、color() 等。在Column和Row容器组件中,justifyContent用于设置子组件在主轴方向上的对齐格式,alignItems用于设置子组件在交叉轴方向上的对齐格式。

牛肉胡辣汤 3818

Java高并发编程基础三大利器之CyclicBarrier

点击上方“Java金融”,选择“设为星标”后台回复"888"获取bat面试题集引言前面一篇文章我们《Java高并发编程基础三大利器之CountDownLatch》它有一个缺...

root 168

基于YOLOv8改进的目标检测算法研究及毕设实现全流程指南

在PyTorch中定义改进后的YOLOv8模型。首先,导入相关的库和模块,然后定义骨干网络、颈部和头部的结构。# 这里定义骨干网络的结构,例如使用改进的CSP结构# 卷积层、批归一化层、激活函数等# 更多的卷积层、批归一化层、激活函数等# 其他层...# 其他层的输出...然后,定义颈部和头部的结构,类似地,将注意力机制和特征增强方法集成到模型中。本毕设以YOLOv8为基础,通过集成注意力机制、应用特征增强技术等方法,改进了目标检测算法。

m0_64661770的博客 323

鲁棒控制理论 --- H∞ 控制(被裁篇)

鲁棒控制理论 --- H∞ 控制

记录老板的每次pua,分享领导每天的毒鸡汤... 6万+

Java 并发工具类:CountDownLatch 和 CyclicBarrier——线程同步的“协作利器”

和是Java并发包中解决线程同步问题的核心工具。前者适合“一次性等待”场景(如主线程等待子任务),后者适合“多阶段齐步走”场景(如分阶段计算)。掌握它们的核心差异和使用技巧,能让你在多线程开发中高效解决协作问题,避免死锁和资源浪费。下一次遇到多线程同步需求时,不妨问自己:“用CountDownLatch还是CyclicBarrier更合适?”——这可能是优化并发性能的关键一步!

yue_yun_的博客 755

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

并发编程原理与实战(七)详解并发协同利器CyclicBarrier

本文详细讲解了循环屏障CyclicBarrier的主要方法,通过“同时对接口发起多次压测”这个例子,和用CountDownLatch实现对比,说明了循环屏障的使用。

真战 1289

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

CyclicBarrier多任务协同的利器

疫情逐渐好转,部门也有半年多没有TB团建了,并且金三银四,部门又招了一波新人; leader让你组织一次TB:周六上午,大家先到公司集合,然后一起去朝阳公园玩,最后一起去餐厅聚餐,然后回家。 为了体现团队集体意识,在每次开启新项目时,需要所有人一起开始行动(不能早来的人都把东西吃光了吧~),并且每个阶段活动完成后,需要统计人数、向上汇报。 这个场景,如何借助JUC并发工具来实现呢? 我们先来梳理一下,任务特点: 很显然,每次开启新项目时,需要所有人一起开始行动 这是个多任务相互等待,直到所有人都到达一个点

ljheee的博客 1560

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

深入解析CyclicBarrier:Java并发编程的协同之利器

这为我们提供了一种方便的方式来执行一些需要在所有线程都准备好之后才能进行的操作,比如数据的汇总或资源的初始化等。此时,所有等待的线程都会被唤醒,并可以继续执行后续的任务。是一个非常有趣且实用的类,它提供了一种机制,允许一组线程互相等待,直到所有线程都达到某个屏障(barrier)点。是一个非常实用的并发工具类,它提供了一种方便的方式来同步一组线程的执行。在这个示例中,我们创建了5个线程,每个线程都会模拟处理数据,并在处理完成后调用。,顾名思义,是一个可以循环使用的屏障。会唤醒所有的线程,并执行我们在构造。

u012808915的博客 528

并发编程之 CyclicBarrier 详解

本文深入分析了Java并发工具类CyclicBarrier的实现原理及应用。CyclicBarrier允许线程在特定屏障点同步等待,当所有线程到达后继续执行。文章首先介绍了其基本概念、构造方法和核心API,通过示例展示了其使用方式。随后重点解析了源码实现,包括内部成员变量(ReentrantLock、Condition等)、构造方法和核心await/dowait方法的工作原理。CyclicBarrier通过计数器机制和代(generation)概念实现线程同步,支持超时等待和屏障重置功能。该工具类适用于需要

qq_844085127的博客 845

Java中的CyclicBarrier详解及其在并发编程中的应用

比如,在一个数据处理系统中,多个线程可以分别加载不同部分的数据,然后使用CyclicBarrier等待所有数据加载完成后进行下一步的数据处理操作。这种机制在并发编程中具有重要的作用,能够帮助开发人员协调多个线程的执行,实现复杂的并发控制。在实际应用中,开发人员可以根据具体的业务需求,灵活地运用CyclicBarrier来实现线程间的协同工作,从而提升程序的性能和效率。总的来说,CyclicBarrier是Java中一个强大的并发控制工具,能够让线程在特定的执行点上实现同步,让它们一起协同工作。

qq_40427481的博客 605

Java并发编程三大同步工具:用CountDownLatch、CyclicBarrier、Semaphore搞定线程协调!

本文介绍了Java并发编程中的三大同步工具:CountDownLatch、CyclicBarrier和Semaphore。CountDownLatch作为一次性倒计时器,适用于主线程等待多个子线程完成的场景;CyclicBarrier是可循环使用的屏障,适合多阶段任务的线程协同;Semaphore则用于控制资源访问的并发数量。文章通过实际代码示例展示了各工具的使用方法,并分析了其底层实现原理。这些同步工具能有效解决多线程协作中的同步问题,使并发程序更加可靠高效。

热爱技术、喜欢学习、乐于分享 1100
上一篇: Redis String 类型的底层实现与性能优化
下一篇: 深入 Java 线程池:核心线程数动态调整技术详解与实战
程序员职业指南
博客等级 码龄4年 2136粉丝 960原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值