一、前言

  有了前面分析的基础,现在,接着分析CyclicBarrier源码,CyclicBarrier类在进行多线程编程时使用很多,比如,你希望创建一组任务,它们并行执行工作,然后在进行下一个步骤之前等待,直至所有的任务都完成,和join很类似,下面,开始分析源码。

二、CyclicBarrier数据结构

  分析源码可以知道,CyclicBarrier底层是基于ReentrantLockAbstractQueuedSynchronizer来实现的,所以,CyclicBarrier的数据结构也依托于AQS的数据结构,在前面对AQS的分析中已经指出了其数据结构,在这里不再累赘。

三、CyclicBarrier源码分析

  3.1 类的继承关系

  1. public class CyclicBarrier {}

  说明:可以看到CyclicBarrier没有显示继承哪个父类或者实现哪个父接口,根据Java语言规定,可知其父类是Object。

  3.2 类的内部类

  CyclicBarrier类存在一个内部类Generation,每一次使用的CycBarrier可以当成Generation的实例,其源代码如下

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

  说明:Generation类有一个属性broken,用来表示当前屏障是否被损坏。

  3.3 类的属性 

  1. public class CyclicBarrier {
  2.  
  3. /** The lock for guarding barrier entry */
  4. // 可重入锁
  5. private final ReentrantLock lock = new ReentrantLock();
  6. /** Condition to wait on until tripped */
  7. // 条件队列
  8. private final Condition trip = lock.newCondition();
  9. /** The number of parties */
  10. // 参与的线程数量
  11. private final int parties;
  12. /* The command to run when tripped */
  13. // 由最后一个进入 barrier 的线程执行的操作
  14. private final Runnable barrierCommand;
  15. /** The current generation */
  16. // 当前代
  17. private Generation generation = new Generation();
  18. // 正在等待进入屏障的线程数量
  19. private int count;
  20. }

说明:该属性有一个为ReentrantLock对象,有一个为Condition对象,而Condition对象又是基于AQS的,所以,归根到底,底层还是由AQS提供支持。

  3.4 类的构造函数

  1. CyclicBarrier(int, Runnable)型构造函数 

  1. public CyclicBarrier(int parties, Runnable barrierAction) {
  2. // 参与的线程数量小于等于0,抛出异常
  3. if (parties <= 0) throw new IllegalArgumentException();
  4. // 设置parties
  5. this.parties = parties;
  6. // 设置count
  7. this.count = parties;
  8. // 设置barrierCommand
  9. this.barrierCommand = barrierAction;
  10. }

说明:该构造函数可以指定关联该CyclicBarrier的线程数量,并且可以指定在所有线程都进入屏障后的执行动作,该执行动作由最后一个进行屏障的线程执行。

  2. CyclicBarrier(int)型构造函数 

  1. public CyclicBarrier(int parties) {
  2. // 调用含有两个参数的构造函数
  3. this(parties, null);
  4. }

说明:该构造函数仅仅执行了关联该CyclicBarrier的线程数量,没有设置执行动作。

  3.5 核心函数分析

  1. dowait函数

  此函数为CyclicBarrier类的核心函数,CyclicBarrier类对外提供的await函数在底层都是调用该了doawait函数,其源代码如下。

  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. // 保存当前代
  10. final Generation g = generation;
  11.  
  12. if (g.broken) // 屏障被破坏,抛出异常
  13. throw new BrokenBarrierException();
  14.  
  15. if (Thread.interrupted()) { // 线程被中断
  16. // 损坏当前屏障,并且唤醒所有的线程,只有拥有锁的时候才会调用
  17. breakBarrier();
  18. // 抛出异常
  19. throw new InterruptedException();
  20. }
  21.  
  22. // 减少正在等待进入屏障的线程数量
  23. int index = --count;
  24. if (index == 0) { // 正在等待进入屏障的线程数量为0,所有线程都已经进入
  25. // 运行的动作标识
  26. boolean ranAction = false;
  27. try {
  28. // 保存运行动作
  29. final Runnable command = barrierCommand;
  30. if (command != null) // 动作不为空
  31. // 运行
  32. command.run();
  33. // 设置ranAction状态
  34. ranAction = true;
  35. // 进入下一代
  36. nextGeneration();
  37. return 0;
  38. } finally {
  39. if (!ranAction) // 没有运行的动作
  40. // 损坏当前屏障
  41. breakBarrier();
  42. }
  43. }
  44.  
  45. // loop until tripped, broken, interrupted, or timed out
  46. // 无限循环
  47. for (;;) {
  48. try {
  49. if (!timed) // 没有设置等待时间
  50. // 等待
  51. trip.await();
  52. else if (nanos > 0L) // 设置了等待时间,并且等待时间大于0
  53. // 等待指定时长
  54. nanos = trip.awaitNanos(nanos);
  55. } catch (InterruptedException ie) {
  56. if (g == generation && ! g.broken) { // 等于当前代并且屏障没有被损坏
  57. // 损坏当前屏障
  58. breakBarrier();
  59. // 抛出异常
  60. throw ie;
  61. } else { // 不等于当前带后者是屏障被损坏
  62. // We're about to finish waiting even if we had not
  63. // been interrupted, so this interrupt is deemed to
  64. // "belong" to subsequent execution.
  65. // 中断当前线程
  66. Thread.currentThread().interrupt();
  67. }
  68. }
  69.  
  70. if (g.broken) // 屏障被损坏,抛出异常
  71. throw new BrokenBarrierException();
  72.  
  73. if (g != generation) // 不等于当前代
  74. // 返回索引
  75. return index;
  76.  
  77. if (timed && nanos <= 0L) { // 设置了等待时间,并且等待时间小于0
  78. // 损坏屏障
  79. breakBarrier();
  80. // 抛出异常
  81. throw new TimeoutException();
  82. }
  83. }
  84. } finally {
  85. // 释放锁
  86. lock.unlock();
  87. }
  88. }

此方法是多个线程中调用的,调用此方法表示到达了屏障位置,并且每个线程调用此方法的时候都是需要通过计数来判断是否全都到达屏障位置。所以CyclicBarrier使用ReentrantLock在方法开始就加了锁

说明:dowait方法的逻辑会进行一系列的判断,大致流程如下。

  2. nextGeneration函数 

  此函数在所有线程进入屏障后会被调用,即生成下一个版本,所有线程又可以重新进入到屏障中,其源代码如下

  1. private void nextGeneration() {
  2. // signal completion of last generation
  3. // 唤醒所有线程
  4. trip.signalAll();
  5. // set up next generation
  6. // 恢复正在等待进入屏障的线程数量
  7. count = parties;
  8. // 新生一代
  9. generation = new Generation();
  10. }

在此函数中会调用AQS的signalAll方法,即唤醒所有等待线程。如果所有的线程都在等待此条件,则唤醒所有线程。其源代码如下

  1. public final void signalAll() {
  2. if (!isHeldExclusively()) // 不被当前线程独占,抛出异常
  3. throw new IllegalMonitorStateException();
  4. // 保存condition队列头结点
  5. Node first = firstWaiter;
  6. if (first != null) // 头结点不为空
  7. // 唤醒所有等待线程
  8. doSignalAll(first);
  9. }

说明:此函数判断头结点是否为空,即条件队列是否为空,然后会调用doSignalAll函数,doSignalAll函数源码如下

  1. private void doSignalAll(Node first) {
  2. // condition队列的头结点尾结点都设置为空
  3. lastWaiter = firstWaiter = null;
  4. // 循环
  5. do {
  6. // 获取first结点的nextWaiter域结点
  7. Node next = first.nextWaiter;
  8. // 设置first结点的nextWaiter域为空
  9. first.nextWaiter = null;
  10. // 将first结点从condition队列转移到sync队列
  11. transferForSignal(first);
  12. // 重新设置first
  13. first = next;
  14. } while (first != null);
  15. }

说明:此函数会依次将条件队列中的节点转移到同步队列中,会调用到transferForSignal函数,其源码如下

  1. final boolean transferForSignal(Node node) {
  2. /*
  3. * If cannot change waitStatus, the node has been cancelled.
  4. */
  5. if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
  6. return false;
  7.  
  8. /*
  9. * Splice onto queue and try to set waitStatus of predecessor to
  10. * indicate that thread is (probably) waiting. If cancelled or
  11. * attempt to set waitStatus fails, wake up to resync (in which
  12. * case the waitStatus can be transiently and harmlessly wrong).
  13. */
  14. Node p = enq(node);
  15. int ws = p.waitStatus;
  16. if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
  17. LockSupport.unpark(node.thread);
  18. return true;
  19. }

说明:此函数的作用就是将处于条件队列中的节点转移到同步队列中,并设置结点的状态信息,其中会调用到enq函数,其源代码如下。

  1. private Node enq(final Node node) {
  2. for (;;) { // 无限循环,确保结点能够成功入队列
  3. // 保存尾结点
  4. Node t = tail;
  5. if (t == null) { // 尾结点为空,即还没被初始化
  6. if (compareAndSetHead(new Node())) // 头结点为空,并设置头结点为新生成的结点
  7. tail = head; // 头结点与尾结点都指向同一个新生结点
  8. } else { // 尾结点不为空,即已经被初始化过
  9. // 将node结点的prev域连接到尾结点
  10. node.prev = t;
  11. if (compareAndSetTail(t, node)) { // 比较结点t是否为尾结点,若是则将尾结点设置为node
  12. // 设置尾结点的next域为node
  13. t.next = node;
  14. return t; // 返回尾结点
  15. }
  16. }
  17. }
  18. }

说明:此函数完成了结点插入同步队列的过程,也很好理解。

  综合上面的分析可知,newGeneration函数的主要方法的调用如下,之后会通过一个例子详细讲解。

  3. breakBarrier函数

  此函数的作用是损坏当前屏障,会唤醒所有在屏障中的线程。源代码如下 

  1. private void breakBarrier() {
  2. // 设置状态
  3. generation.broken = true;
  4. // 恢复正在等待进入屏障的线程数量
  5. count = parties;
  6. // 唤醒所有线程
  7. trip.signalAll();
  8. }

说明:可以看到,此函数也调用了AQS的signalAll函数,由signal函数提供支持。

四、示例

  下面通过一个例子来详解CyclicBarrier的使用和内部工作机制,源代码如下  

  1. package com.hust.grid.leesf.cyclicbarrier;
  2.  
  3. import java.util.concurrent.BrokenBarrierException;
  4. import java.util.concurrent.CyclicBarrier;
  5. /**
  6. *
  7. * @author leesf
  8. * @time 2016.4.16
  9. */
  10. class MyThread extends Thread {
  11. private CyclicBarrier cb;
  12. public MyThread(String name, CyclicBarrier cb) {
  13. super(name);
  14. this.cb = cb;
  15. }
  16.  
  17. public void run() {
  18. System.out.println(Thread.currentThread().getName() + " going to await");
  19. try {
  20. cb.await();
  21. System.out.println(Thread.currentThread().getName() + " continue");
  22. } catch (Exception e) {
  23. e.printStackTrace();
  24. }
  25. }
  26. }
  27. public class CyclicBarrierDemo {
  28. public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
  29. CyclicBarrier cb = new CyclicBarrier(3, new Thread("barrierAction") {
  30. public void run() {
  31. System.out.println(Thread.currentThread().getName() + " barrier action");
  32.  
  33. }
  34. });
  35. MyThread t1 = new MyThread("t1", cb);
  36. MyThread t2 = new MyThread("t2", cb);
  37. t1.start();
  38. t2.start();
  39. System.out.println(Thread.currentThread().getName() + " going to await");
  40. cb.await();
  41. System.out.println(Thread.currentThread().getName() + " continue");
  42.  
  43. }
  44. }

运行结果(某一次): 

  1. t1 going to await
  2. main going to await
  3. t2 going to await
  4. t2 barrier action
  5. t2 continue
  6. t1 continue
  7. main continue

  说明:根据结果可知,可能会存在如下的调用时序。

  说明:由上图可知,假设t1线程的cb.await是在main线程的cb.barrierAction动作是由最后一个进入屏障的线程执行的。根据时序图,进一步分析出其内部工作流程。

  ① main(主)线程执行cb.await操作,主要调用的函数如下。

  说明:由于ReentrantLock的默认采用非公平策略,所以在dowait函数中调用的是ReentrantLock.NonfairSync的lock函数,由于此时AQS的状态是0,表示还没有被任何线程占用,故main线程可以占用,之后在dowait中会调用trip.await函数,最终的结果是条件队列中存放了一个包含main线程的结点,并且被禁止运行了,同时,main线程所拥有的资源也被释放了,可以供其他线程获取。

  ② t1线程执行cb.await操作,其中假设t1线程的lock.lock操作在main线程释放了资源之后,则其主要调用的函数如下。

  说明:可以看到,之后condition queue(条件队列)里面有两个节点,包含t1线程的结点插入在队列的尾部,并且t1线程也被禁止了,因为执行了park操作,此时两个线程都被禁止了。

  ③ t2线程执行cb.await操作,其中假设t2线程的lock.lock操作在t1线程释放了资源之后,则其主要调用的函数如下。

  说明:由上图可知,在t2线程执行await操作后,会直接执行command.run方法,不是重新开启一个线程,而是最后进入屏障的线程执行。同时,会将Condition queue中的所有节点都转移到Sync queue中,并且最后main线程会被unpark,可以继续运行。main线程获取cpu资源,继续运行。

  ④ main线程获取cpu资源,继续运行,下图给出了主要的方法调用。

  说明:其中,由于main线程是在AQS.CO的wait中被park的,所以恢复时,会继续在该方法中运行。运行过后,t1线程被unpark,它获得cpu资源可以继续运行。

  ⑤ t1线程获取cpu资源,继续运行,下图给出了主要的方法调用。

  说明:其中,由于t1线程是在AQS.CO的wait方法中被park,所以恢复时,会继续在该方法中运行。运行过后,Sync queue中保持着一个空节点。头结点与尾节点均指向它。

  注意:在线程await过程中中断线程会抛出异常,所有进入屏障的线程都将被释放。至于CyclicBarrier的其他用法,读者可以自行查阅API,不再累赘。

五、总结

  有了AQS与ReentrantLock的基础,分析CyclicBarrier就会非常简单,因为其底层就是由两者支撑的,关于CycylicBarrier的源码就分析到此,有疑问的读者,欢迎交流,谢谢各位园友的观看~

  

转自:https://www.cnblogs.com/leesf456/p/5392816.html

【JUC】JDK1.8源码分析之CyclicBarrier的更多相关文章

  1. 【JUC】JDK1.8源码分析之CyclicBarrier(四)

    一.前言 有了前面分析的基础,现在,接着分析CyclicBarrier源码,CyclicBarrier类在进行多线程编程时使用很多,比如,你希望创建一组任务,它们并行执行工作,然后在进行下一个步骤之前 ...

  2. 【JUC】JDK1.8源码分析之ArrayBlockingQueue(三)

    一.前言 在完成Map下的并发集合后,现在来分析ArrayBlockingQueue,ArrayBlockingQueue可以用作一个阻塞型队列,支持多任务并发操作,有了之前看源码的积累,再看Arra ...

  3. 【1】【JUC】JDK1.8源码分析之ArrayBlockingQueue,LinkedBlockingQueue

    概要: ArrayBlockingQueue的内部是通过一个可重入锁ReentrantLock和两个Condition条件对象来实现阻塞 注意这两个Condition即ReentrantLock的Co ...

  4. 【1】【JUC】JDK1.8源码分析之ReentrantLock

    概要: ReentrantLock类内部总共存在Sync.NonfairSync.FairSync三个类,NonfairSync与FairSync类继承自Sync类,Sync类继承自AbstractQ ...

  5. 【集合框架】JDK1.8源码分析HashSet && LinkedHashSet(八)

    一.前言 分析完了List的两个主要类之后,我们来分析Set接口下的类,HashSet和LinkedHashSet,其实,在分析完HashMap与LinkedHashMap之后,再来分析HashSet ...

  6. 【集合框架】JDK1.8源码分析之HashMap(一) 转载

    [集合框架]JDK1.8源码分析之HashMap(一)   一.前言 在分析jdk1.8后的HashMap源码时,发现网上好多分析都是基于之前的jdk,而Java8的HashMap对之前做了较大的优化 ...

  7. 【集合框架】JDK1.8源码分析之ArrayList详解(一)

    [集合框架]JDK1.8源码分析之ArrayList详解(一) 一. 从ArrayList字表面推测 ArrayList类的命名是由Array和List单词组合而成,Array的中文意思是数组,Lis ...

  8. 集合之TreeSet(含JDK1.8源码分析)

    一.前言 前面分析了Set接口下的hashSet和linkedHashSet,下面接着来看treeSet,treeSet的底层实现是基于treeMap的. 四个关注点在treeSet上的答案 二.tr ...

  9. 集合之LinkedHashSet(含JDK1.8源码分析)

    一.前言 上篇已经分析了Set接口下HashSet,我们发现其操作都是基于hashMap的,接下来看LinkedHashSet,其底层实现都是基于linkedHashMap的. 二.linkedHas ...

随机推荐

  1. 伪静态与重定向--RewriteBase

    RewriteBase用于设置目录级重写的基准URL,即所有的重定向都是基于这个URL.内部重定向可能看不出效果,但是在外部重定向(使用R flag后),如果不手动指定 / 为根目录,那么就会去整个磁 ...

  2. 在win10开启HyperV(Pro以上版本)安装的Docker,如何远程管理其他机器(Linux或者Win)的docker容器

    用k8s能直接管理吗? 不把那个容器加入集群,可以吗?

  3. Docker for windows 入门三(PowerShell命令使用)

  4. [转帖]Nginx 的 TCP 负载均衡介绍

    Nginx 的 TCP 负载均衡介绍 https://www.cnblogs.com/felixzh/ 前几天同事问 nginx的代理 当时以为只有http的 现在看起来还有tcp的可以使用tcp 代 ...

  5. python常用命令和基础运算符

    基础运算符 http://www.cnblogs.com/alex3714/articles/5465198.html 身份运算符:is is not成员运算符:in not in ##in 判断元素 ...

  6. Test Scenarios for result grid

    1 Page loading symbol should be displayed when it is taking more than default time to load the resul ...

  7. DevexpressVCL v51

    Dev经典套件v49版 支持Delphi2010 DevExpress公司出品的Borland Delphi和C++ Builder的控件(包含完整源代码).ExpressVerticalGrid:就 ...

  8. Java之StringBuffer使用方法

    package basic; //StringBuffer的使用方法,用于保存频繁修改的字符串 public class StringBufferDemo { public static void m ...

  9. 自学Python6.4-内置模块(2)

    自学Python之路-Python基础+模块+面向对象自学Python之路-Python网络编程自学Python之路-Python并发编程+数据库+前端自学Python之路-django 自学Pyth ...

  10. 架构师成长之路4.4-多维监控体系_zabbix

    点击返回架构师成长之路 点击返回:自学Zabbix之路 点击返回:自学Zabbix4.0之路 点击返回:自学zabbix集锦 架构师成长之路4.4-多维监控体系_zabbix 自学Zabbix之路[第 ...