Java 多线程之 CyclicBarrier(并行任务/数据加载/同步辅助类)

一、概述

  • CyclicBarrier(循环屏障)是Java并发编程中的一种同步辅助工具。它允许一组线程相互等待,直到所有线程都到达一个共同的屏障点,然后继续执行后续操作。CyclicBarrier可以用于解决多线程任务的协调和同步问题。

  • CyclicBarrier 的主要作用是使多个线程在某个点上进行同步等待所有线程都到达该点后再一起继续执行

    • 它类似于一组线程从启动开始跑步,跑到3000米时停下等待其他线程,全部跑到3000米后裁判统计分数,然后再同时开始启跑。
    • 我感觉也有点像三峡大坝的船过闸一样的,假如一些船开到三峡大坝的闸内,但是他们不能过闸,但等一定数量的船后,闸门打开,这些船只开始继续航行。
    • 实际应用如获取N个数据碎片,都完成后按逻辑合成一个,然后再进行分析。
  • CyclicBarrier的主要特点如下:

    • 可以重复使用:一旦所有线程都到达屏障点,屏障就会打开,线程可以继续执行后续操作,然后CyclicBarrier可以被重置并再次使用。
    • 计数器机制:CyclicBarrier内部维护了一个计数器,通过指定计数器的值来确定需要等待的线程数量。有没有觉得他像 CountDownLatch
    • 等待线程阻塞:当一个线程到达屏障点时,它会调用await()方法,然后进入阻塞状态,直到所有线程都到达屏障点。
    • 屏障动作:可以为CyclicBarrier指定一个可选的屏障动作(Runnable),当所有线程都到达屏障点时,最后一个到达的线程将执行该屏障动作。
  • CyclicBarrier 常见用途:

    • 并行计算:CyclicBarrier 可以用于将计算任务分解给多个线程并行执行,等待所有线程完成后再进行下一步操作。
    • 数据加载:CyclicBarrier 可以用于多个线程加载数据,等待所有数据加载完成后再进行后续处理。
    • 任务拆分与合并:CyclicBarrier 可以用于将一个大任务拆分为多个子任务,每个子任务由一个线程负责执行,等待所有子任务完成后再进行结果合并。
  • CyclicBarrier 的主要方法包括:

    • await():使当前线程等待其他线程,直到所有线程都调用了 await() 方法,然后所有线程同时继续执行。

    • reset():重置 CyclicBarrier 的状态(屏障和计数器),可以重新使用。

    • getParties():获取参与同步的线程数量。

    • getNumberWaiting():用于获取当前在屏障处等待的线程数量等

  • 注意:CountDownLatch 是计数减操作,设置初始值,减到为0放行。而 CyclicBarrier 是计数加操作,加到目标值后执行一个动作后放行。

二、使用方法

  • 一般用法:声明 CyclicBarrier 对象 barrier,并指定要等待的任务数和回调函数。然后在其他任务中调用 barrier.await ,当有指定数量(下面是3个)任务调用了 barrier.await ,则执行 CyclicBarrier 的回调函数。

    import java.util.concurrent.BrokenBarrierException;
    import java.util.concurrent.CyclicBarrier;
    
    public class CyclicBarrierExample {
        private static final int THREAD_COUNT = 3;
    
        public static void main(String[] args) {
            CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT, () -> {
                // 执行屏障动作(最后一个到达的线程执行该动作)。
                // 特别说明:能执行到这里说明,规定数量的线程(THREAD_COUNT)都执行了 barrier.await();            
            });
    
            for (int i = 0; i < THREAD_COUNT; i++) {
                Thread thread = new Thread(() -> {
                    try {
                        // 执行业务逻辑
                        
                        barrier.await(); // 线程到达屏障点,等待其他线程
                        
                        // 所以线程都到达屏障点,并且已经执行屏障作用后,才会执行这里的代码
                    } catch (InterruptedException | BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                });
                thread.start();
            }
        }
    }
    

三、测试示例1

  • 在下面示例中,创建了一个CyclicBarrier对象,并将计数器的值设置为3,表示需要等待3个线程到达屏障点。然后,我们创建了3个线程,并在每个线程中执行一段业务逻辑后调用await()方法,即表示线程到达屏障点并等待其他线程。当所有线程都到达屏障点时,最后一个到达的线程将执行屏障动作(在示例中为打印一条消息)。执行屏障动作后再继续执行线程任务。

    package top.yiqifu.study.p004_thread;
    
    import java.util.concurrent.BrokenBarrierException;
    import java.util.concurrent.CyclicBarrier;
    
    public class Test071_CyclicBarrier {
        private static final int THREAD_COUNT = 3;
    
        public static void main(String[] args) {
            CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT, () -> {
                // 特别说明:能执行到这里说明,规定数量的线程(THREAD_COUNT)都执行了 barrier.await();
                String threadName = Thread.currentThread().getName();
                System.out.println(threadName+"执行屏障动作(最后一个到达的线程执行该动作)。");
            });
    
            for (int i = 0; i < THREAD_COUNT; i++) {
                Thread thread = new Thread(() -> {
                    try {
                        String threadName = Thread.currentThread().getName();
    
                        System.out.println(threadName+ "执行业务逻辑");
    
                        System.out.println(threadName+ "到达屏障点,等待其他线程");//注意这里仅示意,由于这句和下面的 barrier.await(); 并不是原子操作,所以有可能输出显示的不是最后
                        barrier.await();
    
                        // 所以线程都到达屏障点,并且已经执行屏障作用后,才会执行这里的代码
                        System.out.println(threadName+ "完成业务逻辑");
                    } catch (InterruptedException | BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                });
                thread.setName("线程"+(i+1)+" ");
                thread.start();
            }
        }
    }
    
    

四、测试示例2

  • 下面示例中,假设三个玩家打游戏BOSS,攻击3次为一个阶段。每个玩家攻击三次,若没有击杀BOSS,则BOSS回血50%,然后玩家继续挑战BOSS的示例代码。

    package top.yiqifu.study.p004_thread;
    
    import java.util.concurrent.BrokenBarrierException;
    import java.util.concurrent.CyclicBarrier;
    
    public class Test071_CyclicBarrierGame {
    
    
        private static final int THREAD_COUNT = 3;
    
        public static void main(String[] args) {
            CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT, () -> {
                // 三次没有杀死,Boss 回血 50%
                GameBoss.Instance.enhancement();
            });
    
            for (int i = 0; i < THREAD_COUNT; i++) {
                Thread thread = new Thread(() -> {
                    try {
                        String threadName = Thread.currentThread().getName();
                        double attckVal = 0;
    
                        for (int j = 0; j < 3; j++) {
                            attckVal = Math.random() * 100 % 15;
                            if(GameBoss.Instance.attack(threadName, attckVal, j+1)){
                                return;
                            }
                            Thread.sleep((int)(Math.random() * 1000 % 1000)); // 模拟玩家操作延时
                        }
    
                        barrier.await();
    
                        for (int m = 0; ; m++) {
                            attckVal = Math.random() * 100 % 15;
                            if(GameBoss.Instance.attack(threadName, attckVal, m+3)){
                                return;
                            }
                            Thread.sleep((int)(Math.random() * 1000 % 1000)); // 模拟玩家操作延时
                        }
                    } catch (InterruptedException | BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                });
                thread.setName("玩家"+(i+1)+" ");
                thread.start();
            }
        }
    
    
        public static class GameBoss
        {
            public static  GameBoss Instance = new GameBoss();
    
            private GameBoss(){
    
            }
    
            private double bossValue = 100;
    
            public void enhancement(){
                bossValue = Math.max(bossValue * 1.5, 100);
                System.out.println("Boss 回血 50%");
            }
    
            public synchronized boolean attack(String player, double val, int times){
                if(bossValue < 0){
                    return true;
                }
    
                bossValue -= val;
                System.out.println(player+ "第"+times+"次发动攻击,伤害="+val);
    
                if(bossValue < 0){
                    System.out.println(player+ "击杀");
                    return true;
                }
    
                return false;
            }
        }
    }
    
    

    示例中, GameBoss 类的 attack 使用到了 synchronized 关键字,有关 synchronized 关键字请看 synchronized 使用说明

  • 19
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

QIFU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值