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. javascript Date 格式化
  2. 未来耳机可能将成为最强大的健康监护仪
  3. Entity Framework 5.0基础系列
  4. 图像的灰度级数越多越好_数字图像处理:Reducing Gray Levels, Zooming and Shrinking
  5. 4大JVM性能分析工具详解,及内存泄漏分析方案
  6. IIoT 安防保卫战一触即发,Fortinet 亮剑
  7. c# mysql 汉字乱码_c#+mysql 中文乱码
  8. sql 更新一些特殊要求字段(批量)
  9. (百看不如一练系列)整理的40个前端练手项目|||实践出真知
  10. 分解GIF图片、合成GIF图片
  11. Taro 3 正式版发布!京东推出开放式跨端跨框架方案,这些React、Nerv、Vue、jQuey都能支持上了!...
  12. 【ansible】如何将ansible jinja2的双花括号转义?
  13. 共享储物柜app开发方案
  14. Python爬虫及其它函数知识读记及简单用法,持续更新中...
  15. discuz 配置 上传远程附件
  16. php 查看nts,nts | php教程|php源码|php学习
  17. 2020电赛A题(心电波形,lcd显示,心率计算,iir滤波处理)
  18. 2021年微软服务器出问题,2021年2月“微软补丁日”Windows 多个严重高危漏洞预警...
  19. 零基础快速打造一个属于自己的微信聊天工具
  20. 基于单片机的红外报警系统设计

热门文章

  1. 设置ipv6全球地址和默认网关
  2. python读取多种格式文件(txt,csv,json,sqlite)
  3. vue设置scrollLeft 一直为0的原因
  4. HDD和SSD的比较
  5. 小米“放空枪”:造车五年之期已到,封印解除后能饭否?
  6. 数字电路三种基本逻辑运算关系:与运算、或运算、非运算
  7. NVIDIA GeForce Experience登录不了解决方法[ 氵]
  8. 数控切割机造船行业应用
  9. httpd实现https
  10. 使用tsocks做代理