CyclicBarrier允许一组线程在到达某个栅栏点(common barrier point)互相等待,直到最后一个线程到达栅栏点,栅栏才会打开,处于阻塞状态的线程恢复继续执行。

举例

举个例子来说明CyclicBarrier的使用:

比如吃鸡游戏4排,需要等4个队友均点击准备才可以开启比赛。

public class CyclicBarrierTest {static class Player implements Runnable{private String id;private CyclicBarrier cyclicBarrier;public Player() {}public Player(String id, CyclicBarrier cyclicBarrier) {this.id = id;this.cyclicBarrier = cyclicBarrier;}@Overridepublic void run() {try{System.out.println(System.currentTimeMillis() + ":##" + id + "##开始赛前准备");Thread.sleep((long) (Math.random() * 10000));System.out.println(System.currentTimeMillis() + ":##" + id + "##准备完毕");cyclicBarrier.await();System.out.println(System.currentTimeMillis() + ":##" + id + "##进入刺激战场");Thread.sleep((long) (Math.random() * 10000));System.out.println(System.currentTimeMillis() + ":##" + id + "##成盒");}catch (Exception e){e.printStackTrace();}}}public static void main(String[] args) {ExecutorService service = Executors.newCachedThreadPool();String[] ids = new String[]{"吃鸡帅萌新", "草丛伏地魔", "P城钢枪王", "AWM无敌狙神"};CyclicBarrier barrier = new CyclicBarrier(4);for(int i=0; i<4; i++){service.execute(new Player(ids[i], barrier));}service.shutdown();}
}

点击运行:

1595389608469:##吃鸡帅萌新##开始赛前准备
1595389608469:##草丛伏地魔##开始赛前准备
1595389608470:##P城钢枪王##开始赛前准备
1595389608470:##AWM无敌狙神##开始赛前准备
1595389609325:##草丛伏地魔##准备完毕
1595389609438:##P城钢枪王##准备完毕
1595389615813:##AWM无敌狙神##准备完毕
1595389618075:##吃鸡帅萌新##准备完毕
1595389618075:##吃鸡帅萌新##进入刺激战场
1595389618075:##草丛伏地魔##进入刺激战场
1595389618075:##AWM无敌狙神##进入刺激战场
1595389618075:##P城钢枪王##进入刺激战场
1595389619555:##吃鸡帅萌新##成盒
1595389624833:##草丛伏地魔##成盒
1595389625086:##P城钢枪王##成盒
1595389626059:##AWM无敌狙神##成盒

可以看到,当最后1个人准备好之后,4个人同时进入到刺激战场,相当于同时"冲破栅栏"。

源码剖析

首先看一下构造函数:

public CyclicBarrier(int parties) {this(parties, null);
}public CyclicBarrier(int parties, Runnable barrierAction) {if (parties <= 0) throw new IllegalArgumentException();this.parties = parties;this.count = parties;this.barrierCommand = barrierAction;
}

parties表示需要拦截的线程数,barrierAction主要是为了处理更加复杂的场景,当线程到达栅栏的时候,优先执行barrierAction。

接着看一下await方法:

public int await() throws InterruptedException, BrokenBarrierException {try {return dowait(false, 0L);} catch (TimeoutException toe) {throw new Error(toe); // cannot happen}
}

继续跟dowait方法:

private int dowait(boolean timed, long nanos)throws InterruptedException, BrokenBarrierException,TimeoutException {final ReentrantLock lock = this.lock;// 当前线程获取独占锁lock.lock();try {final Generation g = generation;// 若栅栏已被打破,抛出BrokenBarrierException异常if (g.broken)throw new BrokenBarrierException();// 只要有1个线程被中断,则打破栅栏if (Thread.interrupted()) {breakBarrier();throw new InterruptedException();}// 对count执行减1操作// 最后一个到达栅栏的线程,才会执行下述代码int index = --count;if (index == 0) {  // trippedboolean ranAction = false;try {final Runnable command = barrierCommand;// 若barrierAction不为null,则优先执行barrierActionif (command != null)command.run();ranAction = true;// 创建下一代栅栏nextGeneration();return 0;} finally {if (!ranAction)breakBarrier();}}// 只要不是最后一个线程,就执行自旋,直到栅栏被触发、线程被中断、等待超时for (;;) {try {// 无超时设置if (!timed)// 当前线程被添加到Condition的条件队列中,阻塞挂起trip.await();// 有超时设置else if (nanos > 0L)// 当前线程被添加到Condition的条件队列中,阻塞挂起nanos纳秒nanos = trip.awaitNanos(nanos);} 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();}
}

上述代码还是比较容易理解的,线程依次获取到独占锁,并对count执行减1操作,只要count未变为0,执行trip.await()后,则当前线程会被添加到Condition的条件队列中,阻塞挂起,等待唤醒、中断或超时等待等动作的发生。

public final void await() throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();// 当前线程被添加到Condition的条件等待队列中Node node = addConditionWaiter();// 释放锁long savedState = fullyRelease(node);int interruptMode = 0;while (!isOnSyncQueue(node)) {// 当前线程被阻塞挂起LockSupport.park(this);if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)break;}if (acquireQueued(node, savedState) && interruptMode != THROW_IE)interruptMode = REINTERRUPT;if (node.nextWaiter != null) // clean up if cancelledunlinkCancelledWaiters();if (interruptMode != 0)reportInterruptAfterWait(interruptMode);
}

当最后一个线程获取锁到达栅栏时,count执行减1操作后正好为0,紧接着会执行nextGeneration操作:

private void nextGeneration() {trip.signalAll();count = parties;generation = new Generation();
}

nextGeneration会执行trip.signalAll()将阻塞在trip上的线程依次唤醒,在trip(Condition)的await方法的阻塞处继续往下执行。很明显,会接着执行acquireQueued(node, savedState)方法,各个阻塞线程依次被添加到AQS的同步队列中去,参与获取独占锁的操作。

最后一个线程将其他阻塞线程唤醒后,紧接着会重置count和generation字段,从而实现栅栏的循环利用。

signalAll是Condition的接口方法,但其实现是在AQS中定义的,不清楚的可以去看一下我之前写的AQS源码详解系列,此处不再赘述。

总结

显然,CyclicBarrier是基于ReentrantLock和Condition来实现的,基本原理就是创建1个Condition,然后各个线程依次获取lock,执行Condition的await方法阻塞挂起。当最后一个线程(第parties个)到达栅栏时,会调用nextGeneration方法,唤醒Condition等待队列上的各个阻塞线程,并重置栅栏。唤醒后的线程将依次尝试获取锁执行后续代码。

Java并发工具类--CyclicBarrier详解相关推荐

  1. Java并发编程之CyclicBarrier详解

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

  2. 并发工具类使用详解及区别(CountDownLatch、CyclicBarrier、Semaphore、Exchanger)

    本文转载自:码农历险记 CountDownLatch CountDownLatch介绍 CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程执行完后再执行.例如,应 ...

  3. Java并发工具CountDownLatch使用详解

    本文目录 1.使用场景 2.使用介绍 3.使用案例 4. Thread.join()和CountDownLatch的区别 1.使用场景 通过使用 CountDownLatch可以使当前线程阻塞,等待其 ...

  4. Java并发编程最佳实例详解系列

    Java并发编程最佳实例详解系列: Java并发编程(一)线程定义.状态和属性 Java并发编程(一)线程定义.状态和属性 线程是指程序在执行过程中,能够执行程序代码的一个执行单元.在java语言中, ...

  5. Java并发工具类(闭锁CountDownLatch)

    并发工具类系列: Java并发工具类(闭锁CountDownLatch) Java并发工具类(栅栏CyclicBarrier) Java并发工具类(信号量Semaphore) 闭锁是一种同步工具类,可 ...

  6. 彻底理解Java并发:Java并发工具类

    本篇内容包括:Java 并发工具类的介绍.使用方式与 Demo,包括了 CountDownLatch(线程计数器).CyclicBarrier(回环栅栏).Semaphore(信号量) 以及 Exch ...

  7. Java并发工具类(三)Exchanger

    Java并发工具类(三)Exchanger 在J.U.C并发包中提供了一些工具类,可以供我们在日常的开发中,根据不同的情况去进行一些相关的并发控制,具体的类有: CountDownLatch Sema ...

  8. Java并发工具类-循环屏障CyclicBarrier

    CyclicBarrier简介 CyclicBarrier API 构造方法 await方法 reset方法 使用样例 CyclicBarrier源码详解 CyclicBarrier中属性 构造方法及 ...

  9. Java并发工具类:CountDownLatch、Semaphore、CyclicBarrier、Exchanger、Phaser

    本文目录: 1.CountDownLatch(闭锁) 1.CountDownLatch 例子 2.CyclicBarrier(循环栅栏) 1.CyclicBarrier 例子 2.CountDownL ...

最新文章

  1. 在Linux的Eclipse下搭建Android环境
  2. 线程调度四(setDaemon方法的使用)
  3. android 获取屏幕的宽高
  4. 为什么Spring的健康状况会再次下降,下降,上升,上升,上升和下降?
  5. jmeter 生成计数器_使用密码摘要生成器扩展JMeter
  6. ucosII移植要修改的文件
  7. 为什么要用3个通道来表示法线?
  8. Linux服务器挂载ntfs移动硬盘
  9. 在将计算机技术应用于会计工作的初期,所开发的会计核算软件主要用于,2013年会计从业考试《电算化》会计核算软件...
  10. php yyuc框架,如何学习YYUC框架
  11. C++的虚函数表指针vptr
  12. Tarjan的缩点割点概述
  13. c语言中求圆台体积公式,圆台体积公式_圆台体积计算公式(附计算器)
  14. 计算机系统的还原及备份,win7系统备份与还原功能怎么用?win7系统使用备份与还原功能的方法...
  15. java 实现ps功能_java 简单图片,可以实现ps的几个小滤镜
  16. 帆软连接好数据库,字段带有中文的显示乱码解决方案
  17. 求二元函数最大值matlab,利用matlab, 二元函数求最大值
  18. 在电脑上打开手机当前浏览的网页
  19. 前端 原型对象中this的认识
  20. 蓝牙协议spec文档免费下载官网下载(免费)

热门文章

  1. Flutter 中TextField详解
  2. 热更新报错 log.error('[WDS] Errors while compiling. Reload prevented.');
  3. 你是在生活,还是在凑合?
  4. 啊啊啊啊啊,终于解决电脑hardlock.sys蓝屏问题啦,顺利下载modelsim软件
  5. Pwn_7 ROP (2)
  6. Redis 远程连接配置
  7. 正则表达式中的/\\\\/四个反斜杠含义
  8. vue使用iframe传递参数问题
  9. java bitmap 位图
  10. 【题解】vijos P1029 晴天小猪历险记之number(bfs 康托展开)