CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。

文章目录

  • CyclicBarrier简介
  • CyclicBarrier方法
  • CyclicBarrier实例
  • CyclicBarrier和CountDownLatch的区别
  • CyclicBarrier类的源码分析
  • 本文小结

CyclicBarrier简介

CyclicBarrier也是一种多线程并发控制的实用工具,和CountDownLatch一样具有等待计数的功能,但是相比于CountDownLatch功能更加强大。

CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。

为了理解CyclicBarrier,这里举一个通俗的例子。开运动会时,会有跑步这一项运动,我们来模拟下运动员入场时的情况,假设有6条跑道,在比赛开始时,就需要6个运动员在比赛开始的时候都站在起点了,裁判员吹哨后才能开始跑步。跑道起点就相当于“barrier”,是临界点,而这6个运动员就类比成线程的话,就是这6个线程都必须到达指定点了,意味着凑齐了一波,然后才能继续执行,否则每个线程都得阻塞等待,直至凑齐一波即可。cyclic是循环的意思,也就是说CyclicBarrier当多个线程凑齐了一波之后,仍然有效,可以继续凑齐下一波。CyclicBarrier的执行示意图如下:

当多个线程都达到了指定点后,才能继续往下继续执行。这就有点像报数的感觉,假设6个线程就相当于6个运动员,到赛道起点时会报数进行统计,如果刚好是6的话,这一波就凑齐了,才能往下执行。CyclicBarrier在使用一次后,下面依然有效,可以继续当做计数器使用,这是与CountDownLatch的区别之一。这里的6个线程,也就是计数器的初始值6,是通过CyclicBarrier的构造方法传入的。


CyclicBarrier方法

CyclicBarrier方法

下面来看下CyclicBarrier的主要方法

//等到所有的线程都到达指定的临界点
await() throws InterruptedException, BrokenBarrierException //与上面的await方法功能基本一致,只不过这里有超时限制,阻塞等待直至到达超时时间为止
await(long timeout, TimeUnit unit) throws InterruptedException,
BrokenBarrierException, TimeoutException //获取当前有多少个线程阻塞等待在临界点上
int getNumberWaiting()//用于查询阻塞等待的线程是否被中断
boolean isBroken()

//将屏障重置为初始状态。如果当前有线程正在临界点等待的话,将抛出BrokenBarrierException。

void reset()

CyclicBarrier实例

package cn.wideth.util;import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class MyCyclicBarrier {//指定必须有6个运动员到达才行private static CyclicBarrier barrier = new CyclicBarrier(6, () ->System.out.println("所有运动员入场,裁判员一声令下!!!!!"));public static void main(String[] args) {System.out.println("运动员进场中...");ExecutorService service = Executors.newFixedThreadPool(6);for (int i = 0; i < 6; i++) {service.execute(() -> {try {System.out.println(Thread.currentThread().getName() + " 运动员,进场");barrier.await();System.out.println(Thread.currentThread().getName() + "  运动员出发");} catch (Exception e) {e.printStackTrace();}});}}}

运行结果


代码分析

当某个线程调用了await方法之后,就会进入等待状态,并将计数器-1,直到所有线程调用await方法使计数器为0,才可以继续执行,由于计数器可以重复使用,所以我们又叫它循环屏障。

CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。


CyclicBarrier和CountDownLatch的区别

  1. CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset()方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。
  2. CountDownLatch主要用于实现一个或n个线程需要等待其他线程完成某项操作之后,才能继续往下执行,描述的是一个或n个线程等待其他线程的关系,而CyclicBarrier是多个线程相互等待,知道满足条件以后再一起往下执行。描述的是多个线程相互等待的场景
  3. CyclicBarrier还提供其他有用的方法,比如getNumberWaiting方法可以获得CyclicBarrier阻塞的线程数量。isBroken方法用来知道阻塞的线程是否被中断。

CyclicBarrier类的源码分析

内部实际上通过ReentrantLock控制线程以及通过ReentrantLock得到Condition接口,通过condition控制线程。CyclicBarrier的使用则是通过new CyclicBarrier(10)表示可以拦截10个线程,然后在多线程中通过await()方法,会通过Condition接口让线程阻塞,当很多线程调用await()使CyclicBarrier实例中的count=0时则会被condition通过notifyAll唤醒所有线程,然后拦截器重新生成一个Generation ,并count被重新赋值为parties然后CyclicBarrier就可以被重复利用了。

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;public class CyclicBarrier {private final ReentrantLock lock = new ReentrantLock(); // 底层是可重入的非公平锁private final Condition trip = lock.newCondition(); // condition接口private final int parties; // 总数,构造时传入,目的时做屏障器的计数(被设计成常量也是出于此,目的就是为了重复利用屏障器)private int count; // 计数(count则会根据调用await减一,为0后会被重新赋值为parties)private final Runnable barrierCommand; // runable的线程任务private Generation generation = new Generation(); // 存在目的是循环使用CyclicBarrier,相当于每一次循环这个对象就会变具体看 nextGeneration()/*** 构造方法,传入屏障器计数和Runnable接口的任务*/public CyclicBarrier(int parties, Runnable barrierAction) {if (parties <= 0) throw new IllegalArgumentException(); // 如果屏障器的计数非正数则不合法,抛异常this.parties = parties; // 总数this.count = parties;   // 相当于信号量,每调用一次await就会被减1,为0重新赋值为partiesthis.barrierCommand = barrierAction;// 当计数为0时会执行这个Runnable任务}/*** 构造方法*/public CyclicBarrier(int parties) {this(parties, null);}/*静态内部类*/private static class Generation {Generation() {}                 // 构造方法boolean broken;                 // 初始化会是false,如果为true表示CyclicBarrier不再进行循环}/*** 唤醒阻塞线程,进行下一个拦截循环*/private void nextGeneration() {trip.signalAll(); // trip时可重入锁得到的condition接口,调用这个方法会唤醒所有正在等待的线程count = parties;  // 唤醒了所有的线程,因此count计数会重新赋值为parties,相当于重用屏障器generation = new Generation(); // 生成新的}/*** 暂停拦截器作用*/private void breakBarrier() {generation.broken = true;   // 设置为falsecount = parties;            // count重新赋值为parties,再次使用时就是新的循环trip.signalAll();           // 唤醒所有被阻塞的线程}/*** 拦截线程的方法,被await调用传入的是false,0*/private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {final ReentrantLock lock = this.lock; //可重入锁lock.lock(); // 加锁try {final Generation g = generation; // 得到当前拦截器的用于标识的Generationif (g.broken) // 如果为true,说明暂停了拦截(调用了breakBarrier方法)导致throw new BrokenBarrierException();//因为已经被叫停拦截器了所以抛异常if (Thread.interrupted()) { // 当前线程是否被中断breakBarrier(); // 暂停拦截器throw new InterruptedException(); // 抛出被中断异常}// 计算剩余值int index = --count; // 如果执行到这里,说明拦截器正常使用,因此计数减一,相当于使用了一个信号一个意思,而parties则是总信号量// 如果剩余值为0执行任务if (index == 0) {  // 当前线程使用了一个计数后(相当于使用了一个信号量,判断信号量是否被使用完了)如果为0则进入Runnable command = barrierCommand; // 任务if (command != null) { // 如果任务不为空try {command.run(); // 执行任务} catch (Throwable ex) {breakBarrier(); //异常则暂停拦截器并且下面throw异常throw ex;}}nextGeneration(); // 生成下一个循环拦截return 0;//}// 如果剩余值不为0,则将当前线程阻塞/*** 自旋方式降低cpu上下文切换的可能*/for (;;) {//下面的try似乎不满足条件所以看后面try {if (!timed) // 默认传入的是false,!false=true,所以会让线程阻塞trip.await(); // 通过condition接口阻塞当前线程else if (nanos > 0L)// 默认是0所以不大于nanos = trip.awaitNanos(nanos); // 阻塞nanos纳秒,实际默认值是0纳秒} catch (InterruptedException ie) {if (g == generation && ! g.broken) { // 如果等待过程中被修改了则需要抛异常breakBarrier(); // 赞赏他使用拦截器并抛异常throw ie;} else {Thread.currentThread().interrupt(); // 中断当前线程}}if (g.broken)  // 不执行throw new BrokenBarrierException();if (g != generation) // 不会执行return index;if (timed && nanos <= 0L) { // 不会执行breakBarrier();throw new TimeoutException();}}} finally {lock.unlock();//解锁}}// 获得parties的值(拦截器能够拦截线程的总数)public int getParties() {return parties;}/*** 拦截线程,等待通知.最常用方法,本质调用dowait方法*/public int await() throws InterruptedException, BrokenBarrierException {try {return dowait(false, 0L);} catch (TimeoutException toe) {throw new Error(toe); // cannot happen}}/*** 拦截线程等待通知*/public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException {return dowait(true, unit.toNanos(timeout));}/*** 返回generation.broken的值,目的是根据true/false判断是否拦截器是否继续使用,false则暂停使用*/public boolean isBroken() {final ReentrantLock lock = this.lock;lock.lock();try {return generation.broken;} finally {lock.unlock();}}/*** 重置拦截器*/public void reset() {final ReentrantLock lock = this.lock;lock.lock();try {breakBarrier();   // 暂停拦截器nextGeneration(); // 生成下一个循环} finally {lock.unlock();}}/*** 获取正在等待的线程数*/public int getNumberWaiting() {final ReentrantLock lock = this.lock;lock.lock();try {return parties - count; // 相当于总信号量-剩余信号量=正在执行的线程数(在CyclicBarrier则是自旋的线程数)} finally {lock.unlock();}}
}

本文小结

本文详细介绍了CyclicBarrier循环栅栏的基本概念,应用场景,常用方法以及源码分析。

同步工具之CyclicBarrier循环栅栏相关推荐

  1. CyclicBarrier: 循环栅栏

    CyclicBarrier是另外一种多线程并发控制工具.和CountDownLatch非常类似,它也可以实现线程间的计数等待,但它的功能比CountDownLatch更加复杂且强大. CyclicBa ...

  2. Java并发编程系列学习_CountDownLatch倒计时器CyclicBarrier循环栅栏

    一.倒计时器CountDownLatch 在多线程协作完成业务功能时,有时候需要等待其他多个线程完成任务之后,主线程才能继续往下执行业务功能,在这种的业务场景下,通常可以使用Thread类的join方 ...

  3. 同步工具类CyclicBarrier原理及使用

    简介 栅栏类似于闭锁,它能阻塞一组线程直到某个事件的发生.栅栏与闭锁的关键区别在于,所有的线程必须同时到达栅栏位置,才能继续执行.闭锁用于等待事件,而栅栏用于等待其他线程. CyclicBarrier ...

  4. CyclicBarrie(循环栅栏)的作用与用法

    CyclicBarrie的作用与用法 一.CyclicBarrie的作用 CyclicBarrier循环栅栏(循环屏障)是一个多线程同步的辅助工具类,它允许一组线程在到达某个等待屏障点(common ...

  5. 信号量semaphore 读写锁ReadWriteLock 倒计时器CountDownLatch 循环栅栏 CyclicBarrier 线程阻塞工具类LockSupport...

    信号量semaphore    允许多个线程同时访问 读写锁ReadWriteLock   在频繁的读写耗时中,读之间不阻塞 倒计时器CountDownLatch    obj = new Count ...

  6. 非常有用的并发控制-循环栅栏CyclicBarrier

    转载自 非常有用的并发控制-循环栅栏CyclicBarrier 昨天我讲了倒计时器CountDownLatch的应用,它是阻塞线程直到计时器归0的一种等待方式.今天讲的这个循环栅栏CyclicBarr ...

  7. 16_张孝祥_多线程_同步工具CyclicBarrier与CountDownLatch

    转载: CyclicBarrier的用法 CountDownLatch(倒计时计数器)使用说明 参考: CyclicBarrier和CountDownLatch区别 CyclicBarrier Cyc ...

  8. 倒计时器CountDownLatch 和 循环栅栏:CyclicBarrier

    一 概念: 1.在多线程协作完成业务功能时,有时候需要等待其他多个线程完成任务之后,主线程才能继续往下执行业务功能,在这种的业务场景下,通常可以使用Thread类的join方法,让主线程等待被join ...

  9. 循环计数_倒计数器:CountDownLatch | 循环栅栏:CyclicBarrier

    倒计数器:CountDownLatch CountDownLatch 从名字就可以看出其作用:初始化一个计数,然后每次递减,直至为0,然后触发一个动作.只有一个带参构造器: CountDownLatc ...

最新文章

  1. 空腹吃香蕉对身体好吗?哪些水果不宜空腹吃
  2. PyQt5 技术篇-设置窗口启用默认桌面位置,按屏幕比例
  3. ssh secure shell
  4. python中字符移位加密_1.1 移位密码加密解密python实现
  5. matlab读取高光谱影像
  6. AT指令集及S寄存器
  7. gfdmp和mysql,《高性能MySQL》读书笔记--锁、事务、隔离级别
  8. 日常知识点之公开课内存碎片优化(内存池)
  9. Python爬虫:抖音无水印解析,和程序员斗智斗勇的一天
  10. radmin注册密码
  11. 什么是路由守卫?有什么用?
  12. [AMV-GCNs Neurocomputing2021] Adaptive multi-view graph convolutional networks for skeleton-based ac
  13. FPGA设计之门控时钟
  14. 数据中台功能架构和技术选型
  15. 顾泽苍:新一代人工智能——产业推动的核心理论
  16. 有哪一刻你彻底恨上了你的老师?
  17. 【Keras-ResNet】CIFAR-10
  18. 即时消息:消息收发架构
  19. 两相步进电机的控制及其实现
  20. 联想笔记本电量显示为0

热门文章

  1. Hadoop2.6.0学习笔记(八)SPOF解决方案总结
  2. 编程之美 set 8 区间重合判断
  3. spring问题-使用tomcat容器,通过url映射寻找view的时候,会报错
  4. 学生宿舍管理系统--需求说明、概要设计、详细设计
  5. ms12-20 远程桌面(RDP)3389漏洞
  6. 解决hibernate双向关系造成的一方重复执行SQl,或者死循环的问题
  7. Golang入门(3):一天学完GO的进阶语法
  8. 有了解过Elasticsearch的性化搜索方案吗?
  9. 害怕运维做到30岁还一事无成?你应该来这里看看
  10. spark broadcast的TorrentBroadcast实现