CyclicBarrier的基本方法和应用场景实例

本文深入解析了Java并发工具类CyclicBarrier的工作原理及应用,通过实例演示了如何利用CyclicBarrier实现多线程分组计算,展示了其在循环等待场景下的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.CyclicBarrier简介
CyclicBarrier,是JDK1.5的java.util.concurrent并发包中提供的一个并发工具类。

所谓Cyclic即 循环 的意思,所谓Barrier即 屏障 的意思。

所以综合起来,CyclicBarrier指的就是 循环屏障,虽然这个叫法很奇怪,但是确能很好地表示它的作用。

其作用在JDK注释中是这样描述的:

A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point. 
CyclicBarriers are useful in programs involving a fixed sized party of threads that must occasionally wait for each other. 
The barrier is called cyclic because it can be re-used after the waiting threads are released.

翻译过来,如下:

CyclicBarrier是一个同步辅助类,它允许一组线程相互等待直到所有线程都到达一个公共的屏障点。
在程序中有固定数量的线程,这些线程有时候必须等待彼此,这种情况下,使用CyclicBarrier很有帮助。
这个屏障之所以用循环修饰,是因为在所有的线程释放彼此之后,这个屏障是可以重新使用的。
CyclicBarrier的简单理解

其实,我更喜欢[人满发车]这个词来理解CyclicBarrier的作用:

长途汽车站提供长途客运服务。
当等待坐车的乘客到达20人时,汽车站就会发出一辆长途汽车,让这20个乘客上车走人。
等到下次等待的乘客又到达20人是,汽车站就会又发出一辆长途汽车。

CyclicBarrier的应用场景

CyclicBarrier常用于多线程分组计算。

2.CyclicBarrier方法说明
CyclicBarrier提供的方法有:

——CyclicBarrier(parties)

初始化相互等待的线程数量的构造方法。

——CyclicBarrier(parties,Runnable barrierAction)

初始化相互等待的线程数量以及屏障线程的构造方法。

屏障线程的运行时机:等待的线程数量=parties之后,CyclicBarrier打开屏障之前。

举例:在分组计算中,每个线程负责一部分计算,最终这些线程计算结束之后,交由屏障线程进行汇总计算。

——getParties()

获取CyclicBarrier打开屏障的线程数量,也成为方数。

——getNumberWaiting()

获取正在CyclicBarrier上等待的线程数量。

——await()

在CyclicBarrier上进行阻塞等待,直到发生以下情形之一:

在CyclicBarrier上等待的线程数量达到parties,则所有线程被释放,继续执行。
当前线程被中断,则抛出InterruptedException异常,并停止等待,继续执行。
其他等待的线程被中断,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。
其他等待的线程超时,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。
其他线程调用CyclicBarrier.reset()方法,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。
——await(timeout,TimeUnit)

在CyclicBarrier上进行限时的阻塞等待,直到发生以下情形之一:

在CyclicBarrier上等待的线程数量达到parties,则所有线程被释放,继续执行。

  • 当前线程被中断,则抛出InterruptedException异常,并停止等待,继续执行。
  • 当前线程等待超时,则抛出TimeoutException异常,并停止等待,继续执行。
  • 其他等待的线程被中断,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。
  • 其他等待的线程超时,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。
  • 其他线程调用CyclicBarrier.reset()方法,则当前线程抛出BrokenBarrierException异常,并停止等待,继续执行。

——isBroken()

获取是否破损标志位broken的值,此值有以下几种情况:

  • CyclicBarrier初始化时,broken=false,表示屏障未破损。
  • 如果正在等待的线程被中断,则broken=true,表示屏障破损。
  • 如果正在等待的线程超时,则broken=true,表示屏障破损。
  • 如果有线程调用CyclicBarrier.reset()方法,则broken=false,表示屏障回到未破损状态。

——reset()

使得CyclicBarrier回归初始状态,直观来看它做了两件事:

  • 如果有正在等待的线程,则会抛出BrokenBarrierException异常,且这些线程停止等待,继续执行。
  • 将是否破损标志位broken置为false。

3.CyclicBarrier方法练习
3.1.练习一
练习目的:

了解CyclicBarrier(parties)/getParties()/await()/getNumberWaiting()的基本用法。
理解循环的意义。
示例代码:

//构造函数1:初始化-开启屏障的方数
CyclicBarrier barrier0 = new CyclicBarrier(2);
//通过barrier.getParties()获取开启屏障的方数
LOGGER.info("barrier.getParties()获取开启屏障的方数:" + barrier0.getParties());
System.out.println();
//通过barrier.getNumberWaiting()获取正在等待的线程数
LOGGER.info("通过barrier.getNumberWaiting()获取正在等待的线程数:初始----" + barrier0.getNumberWaiting());
System.out.println();
new Thread(() -> {
    //添加一个等待线程
    LOGGER.info("添加第1个等待线程----" + Thread.currentThread().getName());
    try {
        barrier0.await();
        LOGGER.info(Thread.currentThread().getName() + " is running...");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        e.printStackTrace();
    }
    LOGGER.info(Thread.currentThread().getName() + " is terminated.");
}).start();
Thread.sleep(10);
//通过barrier.getNumberWaiting()获取正在等待的线程数
LOGGER.info("通过barrier.getNumberWaiting()获取正在等待的线程数:添加第1个等待线程---" + barrier0.getNumberWaiting());
Thread.sleep(10);
System.out.println();
new Thread(() -> {
    //添加一个等待线程
    LOGGER.info("添加第2个等待线程----" + Thread.currentThread().getName());
    try {
        barrier0.await();
        LOGGER.info(Thread.currentThread().getName() + " is running...");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        e.printStackTrace();
    }
    LOGGER.info(Thread.currentThread().getName() + " is terminated.");
}).start();
Thread.sleep(100);
System.out.println();
//通过barrier.getNumberWaiting()获取正在等待的线程数
LOGGER.info("通过barrier.getNumberWaiting()获取正在等待的线程数:打开屏障之后---" + barrier0.getNumberWaiting());

//已经打开的屏障,再次有线程等待的话,还会重新生效--视为循环
new Thread(() -> {
    LOGGER.info("屏障打开之后,再有线程加入等待:" + Thread.currentThread().getName());
    try {
        //BrokenBarrierException
        barrier0.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        e.printStackTrace();
    }
    LOGGER.info(Thread.currentThread().getName() + " is terminated.");

}).start();
System.out.println();
Thread.sleep(10);
LOGGER.info("通过barrier.getNumberWaiting()获取正在等待的线程数:打开屏障之后---" + barrier0.getNumberWaiting());
Thread.sleep(10);
new Thread(() -> {
    LOGGER.info("屏障打开之后,再有线程加入等待:" + Thread.currentThread().getName());
    try {
        //BrokenBarrierException
        barrier0.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        e.printStackTrace();
    }
    LOGGER.info(Thread.currentThread().getName() + " is terminated.");

}).start();
Thread.sleep(10);
LOGGER.info("通过barrier.getNumberWaiting()获取正在等待的线程数:打开屏障之后---" + barrier0.getNumberWaiting());

运行结果:

2018-04-01 13:27:55 INFO - barrier.getParties()获取开启屏障的方数:2

2018-04-01 13:27:55 INFO - 通过barrier.getNumberWaiting()获取正在等待的线程数:初始----0

2018-04-01 13:27:55 INFO - 添加第1个等待线程----Thread-0
2018-04-01 13:27:55 INFO - 通过barrier.getNumberWaiting()获取正在等待的线程数:添加第1个等待线程---1

2018-04-01 13:27:55 INFO - 添加第2个等待线程----Thread-1
2018-04-01 13:27:55 INFO - Thread-1 is running...
2018-04-01 13:27:55 INFO - Thread-0 is running...
2018-04-01 13:27:55 INFO - Thread-1 is terminated.
2018-04-01 13:27:55 INFO - Thread-0 is terminated.

2018-04-01 13:27:55 INFO - 通过barrier.getNumberWaiting()获取正在等待的线程数:打开屏障之后---0

2018-04-01 13:27:55 INFO - 屏障打开之后,再有线程加入等待:Thread-2
2018-04-01 13:27:55 INFO - 通过barrier.getNumberWaiting()获取正在等待的线程数:打开屏障之后---1
2018-04-01 13:27:55 INFO - 屏障打开之后,再有线程加入等待:Thread-3
2018-04-01 13:27:55 INFO - Thread-3 is terminated.
2018-04-01 13:27:55 INFO - Thread-2 is terminated.
2018-04-01 13:27:55 INFO - 通过barrier.getNumberWaiting()获取正在等待的线程数:打开屏障之后---0

从运行结果,可以更好的理解循环的意义。

4.应用场景
场景说明:

模拟多线程分组计算
有一个大小为50000的随机数组,用5个线程分别计算10000个元素的和
然后在将计算结果进行合并,得出最后的结果。
重点分析:

用5个线程分别计算:定义一个大小为5的线程池。
计算结果进行合并:定义一个屏障线程,将上面5个线程计算的子结果信息合并。
实例代码:

 

/**
* <p>CyclicBarrier-循环屏障-模拟多线程计算</p>
 *
 * @author hanchao 2018/3/29 22:48
 **/
public static void main(String[] args) {
    //数组大小
    int size = 50000;
    //定义数组
    int[] numbers = new int[size];
    //随机初始化数组
    for (int i = 0; i < size; i++) {
        numbers[i] = RandomUtils.nextInt(100, 1000);
    }

    //单线程计算结果
    System.out.println();
    Long sum = 0L;
    for (int i = 0; i < size; i++) {
        sum += numbers[i];
    }
    LOGGER.info("单线程计算结果:" + sum);

    //多线程计算结果
    //定义线程池
    ExecutorService executorService = Executors.newFixedThreadPool(5);
    //定义五个Future去保存子数组计算结果
    final int[] results = new int[5];

    //定义一个循环屏障,在屏障线程中进行计算结果合并
    CyclicBarrier barrier = new CyclicBarrier(5, () -> {
        int sums = 0;
        for (int i = 0; i < 5; i++) {
            sums += results[i];
        }
        LOGGER.info("多线程计算结果:" + sums);
    });

    //子数组长度
    int length = 10000;
    //定义五个线程去计算
    for (int i = 0; i < 5; i++) {
        //定义子数组
        int[] subNumbers = Arrays.copyOfRange(numbers, (i * length), ((i + 1) * length));
        //盛放计算结果
        int finalI = i;
        executorService.submit(() -> {
            for (int j = 0; j < subNumbers.length; j++) {
                results[finalI] += subNumbers[j];
            }
            //等待其他线程进行计算
            try {
                barrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        });
    }

    //关闭线程池
    executorService.shutdown();
}

 

运行结果:

2018-04-01 17:05:47 INFO - 单线程计算结果:27487277
2018-04-01 17:05:47 INFO - 多线程计算结果:27487277

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值