在上篇博客(【Java并发编程实战】-----“J.U.C”:Semaphore)中,LZ介绍了Semaphore,下面LZ介绍CyclicBarrier。在JDK API中是这么介绍的:

一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。

CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作 很有用。

对于失败的同步尝试,CyclicBarrier 使用了一种要么全部要么全不 (all-or-none) 的破坏模式:如果因为中断、失败或者超时等原因,导致线程过早地离开了屏障点,那么在该屏障点等待的其他所有线程也将通过 BrokenBarrierException(如果它们几乎同时被中断,则用 InterruptedException)以反常的方式离开。

CyclicBarrier分析

CyclicBarrier结构如下:

 

从上图可以看到CyclicBarrier内部使用ReentrantLock独占锁实现的。其构造函数如下:

CyclicBarrier(int parties):创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作。

CyclicBarrier(int parties, Runnable barrierAction):创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动 barrier 时执行给定的屏障操作,该操作由最后一个进入 barrier 的线程执行。

  1. public CyclicBarrier(int parties) {
  2. this(parties, null);
  3. }
  4.  
  5. public CyclicBarrier(int parties, Runnable barrierAction) {
  6. if (parties <= 0) throw new IllegalArgumentException();
  7. this.parties = parties;
  8. this.count = parties;
  9. this.barrierCommand = barrierAction;
  10. }

在CyclicBarrier中,最重要的方法就是await(),在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。其源代码如下:

  1. public int await() throws InterruptedException, BrokenBarrierException {
  2. try {
  3. return dowait(false, 0L);
  4. } catch (TimeoutException toe) {
  5. throw new Error(toe); // cannot happen;
  6. }
  7. }

await内部调用dowait():

  1. private int dowait(boolean timed, long nanos)
  2. throws InterruptedException, BrokenBarrierException,
  3. TimeoutException {
  4. //独占锁
  5. final ReentrantLock lock = this.lock;
  6. //获取独占锁
  7. lock.lock();
  8. try {
  9. //保存当前"Generation"
  10. final Generation g = generation;
  11. //当前generation“已损坏”,抛出BrokenBarrierException异常
  12. //抛出该异常一般都是某个线程在等待某个处于“断开”状态的CyclicBarrier
  13. if (g.broken)
  14. throw new BrokenBarrierException();
  15.  
  16. //当前线程中断,通过breakBarrier终止终止CyclicBarrier
  17. if (Thread.interrupted()) {
  18. breakBarrier();
  19. throw new InterruptedException();
  20. }
  21.  
  22. //计数器-1
  23. int index = --count;
  24. //如果计数器 == 0
  25. //表示所有线程都已经到位,触发动作(是否执行某项任务)
  26. if (index == 0) { // tripped
  27. boolean ranAction = false;
  28. try {
  29. //barrierCommand线程要执行的任务
  30. final Runnable command = barrierCommand;
  31. //执行的任务!=null,执行任务
  32. if (command != null)
  33. command.run();
  34. ranAction = true;
  35. //唤醒所有等待线程,并更新generation。
  36. nextGeneration();
  37. return 0;
  38. } finally {
  39. if (!ranAction)
  40. breakBarrier();
  41. }
  42. }
  43.  
  44. //循环一直执行,直到下面三个if一个条件满足才会退出循环
  45. for (;;) {
  46. try {
  47. //如果不是超时等待,则调用await等待
  48. if (!timed)
  49. trip.await();
  50. //调用awaitNanos等待
  51. else if (nanos > 0L)
  52. nanos = trip.awaitNanos(nanos);
  53. } catch (InterruptedException ie) {
  54. //
  55. if (g == generation && ! g.broken) {
  56. breakBarrier();
  57. throw ie;
  58. } else {
  59. Thread.currentThread().interrupt();
  60. }
  61. }
  62.  
  63. //当前generation“已损坏”,抛出BrokenBarrierException异常
  64. //抛出该异常一般都是某个线程在等待某个处于“断开”状态的CyclicBarrier
  65. if (g.broken)
  66. throw new BrokenBarrierException();
  67.  
  68. //generation已经更新,返回index
  69. if (g != generation)
  70. return index;
  71.  
  72. //“超时等待”,并且时间已到,则通过breakBarrier()终止CyclicBarrier
  73. if (timed && nanos <= 0L) {
  74. breakBarrier();
  75. throw new TimeoutException();
  76. }
  77. }
  78. } finally {
  79. //释放独占锁
  80. lock.unlock();
  81. }
  82. }

在dowait方法中其实处理逻辑还是比较简单的:

1、首先判断该barrier是否已经断开了,如果断开则抛出BrokenBarrierException异常;

2、判断计算器index是否等于0,如果等于0,则表示所有的线程准备就绪,已经到达某个公共屏障点了,barrier可以进行后续工作了(是否执行某项任务(构造函数决定));然后调用nextGeneration方法进行更新换代工作(其中会唤醒所有等待的线程);

3、通过for循环(for(;;))使线程一直处于等待状态。直到“有parties个线程到达barrier” 或 “当前线程被中断” 或 “超时”这3者之一发生。

在dowait中有Generation这样一个对象。该对象是CyclicBarrier的一个成员变量:

  1. private static class Generation {
  2. boolean broken = false;
  3. }

Generation描述着CyclicBarrier的更显换代。在CyclicBarrier中,同一批线程属于同一代。当有parties个线程到达barrier,generation就会被更新换代。其中broken标识该当前CyclicBarrier是否已经处于中断状态。

对于中断,CyclicBarrier是通过breakBarrier()实现的:

  1. private void breakBarrier() {
  2. generation.broken = true;
  3. count = parties;
  4. trip.signalAll();
  5. }

在breakBarrier()中除了将broken设置为true,还会调用signalAll将在CyclicBarrier处于等待状态的线程全部唤醒。

在超时的判断中,CyclicBarrier根据timed的值来执行不同的wait。await、awaitNanos都是Condition中的方法。

当index = --count等于0时,标识“有parties个线程到达barrier”,临界条件到达,则执行相应的动作。执行完动作后,则调用nextGeneration进行更新换代:

  1. private void nextGeneration() {
  2. //唤醒所有处于等待状态的线程
  3. trip.signalAll();
  4. //初始化计数器
  5. count = parties;
  6. //产生新的Generation对象
  7. generation = new Generation();
  8. }

示例

1、线程等待到一定条件后才会继续进行。

  1. public class CyclicBarrierTest_1 {
  2. private static CyclicBarrier barrier;
  3.  
  4. static class threadTest1 extends Thread{
  5. public void run() {
  6. System.out.println(Thread.currentThread().getName() + "达到...");
  7. try {
  8. barrier.await();
  9. } catch (Exception e) {
  10. e.printStackTrace();
  11. }
  12. System.out.println(Thread.currentThread().getName() + "执行完成...");
  13. }
  14. }
  15.  
  16. public static void main(String[] args) {
  17. barrier = new CyclicBarrier(5);
  18. for(int i = 1 ; i <= 5 ; i++){
  19. new threadTest1().start();
  20. }
  21. }
  22. }

------执行结果:

  1. Thread-0达到...
  2. Thread-1达到...
  3. Thread-3达到...
  4. Thread-2达到...
  5. Thread-4达到...
  6. Thread-4执行完成...
  7. Thread-0执行完成...
  8. Thread-1执行完成...
  9. Thread-2执行完成...
  10. Thread-3执行完成...

2、线程等待到一定条件后,执行某项任务。比如说我们等车,只有当车坐满后,汽车才会发动。

这个只需要对上面的代码进行小动作的改动即可:

  1. public class CyclicBarrierTest_2 {
  2. private static CyclicBarrier barrier;
  3.  
  4. static class threadTest1 extends Thread{
  5. public void run() {
  6. System.out.println(Thread.currentThread().getName() + "达到...");
  7. try {
  8. barrier.await();
  9. } catch (Exception e) {
  10. e.printStackTrace();
  11. }
  12. System.out.println(Thread.currentThread().getName() + "执行完成...");
  13. }
  14. }
  15.  
  16. public static void main(String[] args) {
  17. barrier = new CyclicBarrier(5,new Runnable() {
  18.  
  19. @Override
  20. public void run() {
  21. System.out.println("执行CyclicBarrier中的任务.....");
  22. }
  23. });
  24. for(int i = 1 ; i <= 5 ; i++){
  25. new threadTest1().start();
  26. }
  27. }
  28. }

-------执行结果:

  1. Thread-0达到...
  2. Thread-1达到...
  3. Thread-3达到...
  4. Thread-4达到...
  5. Thread-2达到...
  6. 执行CyclicBarrier中的任务.....
  7. Thread-2执行完成...
  8. Thread-0执行完成...
  9. Thread-3执行完成...
  10. Thread-1执行完成...
  11. Thread-4执行完成...

 

参考文献:

1、Java多线程系列--“JUC锁”10之 CyclicBarrier原理和示例

【Java并发编程实战】-----“J.U.C”:CyclicBarrier的更多相关文章

  1. 【Java并发编程实战】-----“J.U.C”:CountDownlatch

    上篇博文([Java并发编程实战]-----"J.U.C":CyclicBarrier)LZ介绍了CyclicBarrier.CyclicBarrier所描述的是"允许一 ...

  2. 【Java并发编程实战】-----“J.U.C”:ReentrantReadWriteLock

    ReentrantLock实现了标准的互斥操作,也就是说在某一时刻只有有一个线程持有锁.ReentrantLock采用这种独占的保守锁直接,在一定程度上减低了吞吐量.在这种情况下任何的"读/ ...

  3. 【Java并发编程实战】-----“J.U.C”:Semaphore

    信号量Semaphore是一个控制访问多个共享资源的计数器,它本质上是一个"共享锁". Java并发提供了两种加锁模式:共享锁和独占锁.前面LZ介绍的ReentrantLock就是 ...

  4. 【Java并发编程实战】-----“J.U.C”:ReentrantLock之三unlock方法分析

    前篇博客LZ已经分析了ReentrantLock的lock()实现过程,我们了解到lock实现机制有公平锁和非公平锁,两者的主要区别在于公平锁要按照CLH队列等待获取锁,而非公平锁无视CLH队列直接获 ...

  5. 【Java并发编程实战】-----“J.U.C”:ReentrantLock之一简介

    注:由于要介绍ReentrantLock的东西太多了,免得各位客官看累,所以分三篇博客来阐述.本篇博客介绍ReentrantLock基本内容,后两篇博客从源码级别分别阐述ReentrantLock的l ...

  6. 【Java并发编程实战】----- AQS(四):CLH同步队列

    在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形.其主要从两方面进行了改造:节点的结构与节点等待机制.在结构上引入了头 ...

  7. 【Java并发编程实战】----- AQS(二):获取锁、释放锁

    上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放. AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断. acquireInte ...

  8. 【Java并发编程实战】—– AQS(四):CLH同步队列

    在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形. 其主要从双方面进行了改造:节点的结构与节点等待机制.在结构上引入了 ...

  9. Java并发编程实战 02Java如何解决可见性和有序性问题

    摘要 在上一篇文章当中,讲到了CPU缓存导致可见性.线程切换导致了原子性.编译优化导致了有序性问题.那么这篇文章就先解决其中的可见性和有序性问题,引出了今天的主角:Java内存模型(面试并发的时候会经 ...

随机推荐

  1. .NET 串口通信

    这段时间做了一个和硬件设备通信的小项目,涉及到扫描头.输送线.称重机.贴标机等硬件.和各设备之间通信使用的是串口或网络(Socket)的方式.扫描头和贴标机使用的网络通信,输送线和称重机使用的是串口通 ...

  2. bootstrap-fileinput 简单使用

    bootstrap-fileinput 是一款图片/文件上传 bootstrap 插件,简单示例代码: <!DOCTYPE html> <html> <head> ...

  3. SDWebImage源码解读之SDWebImageCache(下)

    第六篇 前言 我们在SDWebImageCache(上)中了解了这个缓存类大概的功能是什么?那么接下来就要看看这些功能是如何实现的? 再次强调,不管是图片的缓存还是其他各种不同形式的缓存,在原理上都极 ...

  4. JS的内建函数reduce

    @(js) reduce函数,是ECMAScript5规范中出现的数组方法.在平时的工作中,相信大家使用的场景并不多,一般而言,可以通过reduce方法实现的逻辑都可以通过forEach方法来变相的实 ...

  5. Springboot搭建web项目

    最近因为项目需要接触了springboot,然后被其快速零配置的特点惊呆了.关于springboot相关的介绍我就不赘述了,大家自行百度google. 一.pom配置 首先,建立一个maven项目,修 ...

  6. 记录一次bug解决过程:数据迁移

    一 总结 不擅长语言表达,勤于沟通,多锻炼 调试MyBatis中SQL语法:foreach 问题:缺少关键字VALUES.很遗憾:它的错误报的让人找不着北. 二 BUG描述:MyBatis中批量插入数 ...

  7. [转载]网站地址栏小图标favicon.ico的制作方法

    有人也许会好奇,有的网址前面有个漂亮的小图标而且有的网站图标还会动,这是怎么做到的呢? 如下图所示: 那个小图标有个名字叫favicon.ico,网站图标虽小但可以起到很好的点缀作用,尤其是当浏览者将 ...

  8. sqlserver批量修改首字母为大写

    'hello world'  ---->   'Hello world' update tableName set columnName=CHAR(ASCII(SUBSTRING(columnN ...

  9. 跨越语言的障碍:C++/CLI 调用 C#

    首先我想投诉一下博客园首页右边栏的广告..最近总是出现很恐怖的整容脸的广告.真的是吓坏了.=.=大家有同感吗? 博客园前一阵子掀起了语言的广泛讨论,事实上语言的争执在整个程序员圈子也没有停止过.以我个 ...

  10. 前端工程师手中的Sublime Text

    原文地址:http://css-tricks.com/sublime-text-front-end-developers/ 我的Blog:http://cabbit.me/sublime-text-f ...