CyclicBarrier是一种类似于栅栏的存在,意思就是在栅栏开放之前你都只能被挡在栅栏的一侧,当栅栏移除之后,之前被挡在一侧的多个对象则同时开始动起来。

1. 如何使用CyclicBarrier

  在介绍其原理之前,先了解一下CyclicBarrier应该如何使用。

  假设现在有这样的场景,我们需要开一个会议,需要张1、张2、张3三个人参加,

会议需要三个人都到齐之后才能开始,否则只能干等着;这个场景用CyclicBarrier可以很契合的模拟出来。代码如下:

  1. public static void main(String[] args) {
  2. // 线程池,每个线程代表一个人
  3. ThreadPoolExecutor executor = ThreadPoolProvider.getInstance();
  4. // 会议所需的人数为3
  5. CyclicBarrier barrier = new CyclicBarrier(3);
  6. executor.execute(() -> {
  7. try {
  8. System.err.println("张1到达会议室");
  9. barrier.await();
  10. System.err.println("会议开始,张1开始发言");
  11. } catch (InterruptedException | BrokenBarrierException e) {
  12. e.printStackTrace();
  13. }
  14. });
  15. executor.execute(() -> {
  16. try {
  17. System.err.println("张2到达会议室");
  18. barrier.await();
  19. System.err.println("会议开始,张2开始发言");
  20. } catch (InterruptedException | BrokenBarrierException e) {
  21. e.printStackTrace();
  22. }
  23. });
  24. executor.execute(() -> {
  25. try {
  26. System.err.println("张3先去个厕所,内急解决再去开会");
  27. TimeUnit.SECONDS.sleep(1);
  28. System.err.println("张3到达会议室");
  29. barrier.await();
  30. System.err.println("会议开始,张3开始发言");
  31. } catch (InterruptedException | BrokenBarrierException e) {
  32. e.printStackTrace();
  33. }
  34. });
  35. executor.shutdown();
  36. }

结果图:



  通过上方代码可以知道CyclicBarrier的几点:

  1. 使用await()来表示完成了某些事情。(上方例子的表现为到达了会议室)
  2. 使用await()之后当前线程就进入阻塞状态,需要等待完全满足CyclicBarrier的条件后唤醒才能继续接下来的操作。(上方例子中 为3个人都到达会议室)
  3. 在最后一个线程达到条件之后,之前阻塞的线程全部放开,继续接下来的操作。(上方例子为张3到达会议室)

  这个简单的例子也让我们了解CyclicBarrier的使用方法,那来看看其内部究竟是如何实现栅栏的效果的。

2. CyclicBarrier是如何成为"栅栏"的

  从第一节的代码中我们也能看到,需要关注的就两个地方

  1. 构造函数
  2. await()方法

只要了解这两个方法的内部,相当于了解了CyclicBarrier的内部。

那在深入了解之前,先来看下CyclicBarrier的几个变量,不用刻意去记,看代码的时候知道这个东西做什么用的就行了:

lock:CyclicBarrier类创建的ReentrantLock实例,关于ReentrantLock不清楚的可以->传送。

trip:lock中的conditionCyclicBarrier使用该变量来实现各线程之间的阻塞和同时唤醒。同样,不明白condition作用的=>传送门

parties:需要满足条件(调用await方法)的总数,就是说当有parties个线程await()之后就会唤醒全部线程。

barrierCommand:一个Runnable变量,在await方法的调用次数到达总数parties之后,在唤醒全部线程之前执行其run()方法

generation:其内部类,可以理解为周期,周期内需要完成n个任务,只要一个任务失败,当前周期的所有任务就算失败,结束当前周期,再开启下个周期。

count:当前周期剩余需要完成的任务数(剩余调用await方法的次数)

以下为源码:

  1. public class CyclicBarrier {
  2. // 内部类,可理解为周期
  3. private static class Generation {
  4. // 当前周期是否失败
  5. boolean broken = false;
  6. }
  7. // 锁的实例
  8. private final ReentrantLock lock = new ReentrantLock();
  9. // ReentrantLock的condition变量,用来控制线程唤醒和阻塞
  10. private final Condition trip = lock.newCondition();
  11. // 需要满足条件的次数,即需要调用await方法的次数
  12. private final int parties;
  13. // 满足条件次数达到parties之后,唤醒所有线程之前执行其 run()方法
  14. private final Runnable barrierCommand;
  15. // 当前周期
  16. private Generation generation = new Generation();
  17. // 剩余满足条件次数
  18. private int count;
  19. // ...
  20. }

  看完CyclicBarrier的几个变量后,来看其具体的内部实现。

  首先来看构造函数,其构造函数有两个,一个在达到条件总数(parties)后直接叫醒所有线程;另一个指定一个Runnable在达到条件总数后先执行其run()方法再叫醒。

  • 不指定Runnable,参数只有一个:需要达成的任务数
  1. public CyclicBarrier(int parties) {
  2. // 直接调用另一个构造方法,Runnable传null,表示不执行
  3. this(parties, null);
  4. }
  • 指定Runnable的构造方法,赋值任务总数、剩余任务数、唤醒操作之前的Runnable
  1. public CyclicBarrier(int parties, Runnable barrierAction) {
  2. if (parties <= 0) throw new IllegalArgumentException();
  3. // 任务总数
  4. this.parties = parties;
  5. // 剩余需要完成的任务数
  6. this.count = parties;
  7. // 唤醒之前执行的Runnable
  8. this.barrierCommand = barrierAction;
  9. }

  在第一节我们使用的是第一个构造方法,来试试第二个

  1. public static void main(String[] args) throws InterruptedException {
  2. ThreadPoolExecutor executor = ThreadPoolProvider.getInstance();
  3. /** =======增加Runnable,其他地方保持一致=============*/
  4. CyclicBarrier barrier = new CyclicBarrier(3, ()-> System.err.println("在会议开始之前,先给大家发下开会资料"));
  5. executor.execute(() -> {
  6. try {
  7. System.err.println("张1到达会议室");
  8. barrier.await();
  9. System.err.println("会议开始,张1开始发言");
  10. } catch (InterruptedException | BrokenBarrierException e) {
  11. e.printStackTrace();
  12. }
  13. });
  14. executor.execute(() -> {
  15. try {
  16. System.err.println("张2到达会议室");
  17. barrier.await();
  18. System.err.println("会议开始,张2开始发言");
  19. } catch (InterruptedException | BrokenBarrierException e) {
  20. e.printStackTrace();
  21. }
  22. });
  23. executor.execute(() -> {
  24. try {
  25. System.err.println("张3先去个厕所,内急解决再去开会");
  26. TimeUnit.SECONDS.sleep(1);
  27. System.err.println("张3到达会议室");
  28. barrier.await();
  29. System.err.println("会议开始,张3开始发言");
  30. } catch (InterruptedException | BrokenBarrierException e) {
  31. e.printStackTrace();
  32. }
  33. });
  34. executor.shutdown();
  35. }

结果图:

 看完构造函数,就算理解了一半CyclicBarrier了,接下来来看另一半——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. }

直接调用dowait方法,传参为false0,意思就是不限时等待,除非线程被打断或者唤醒。再进入dowait方法,这个方法就是CyclicBarrier的另一半,在下方的代码中很清楚的写了整个执行流程

  1. /** 参数说明, timed:是否限时, nanos:限时时间*/
  2. private int dowait(boolean timed, long nanos)
  3. throws InterruptedException, BrokenBarrierException, TimeoutException {
  4. // 锁
  5. final ReentrantLock lock = this.lock;
  6. // 获取锁,如果失败的话线程睡眠,进入同步队列(AQS中的知识)
  7. lock.lock();
  8. try {
  9. /* 拿到锁之后进入代码处理逻辑*/
  10. // 当前周期
  11. final Generation g = generation;
  12. // 如果当前周期是失败的,那么直接抛错
  13. if (g.broken)
  14. throw new BrokenBarrierException();
  15. // 如果当前线程被打断了,那么此次周期失败,设置相关参数,然后抛错
  16. if (Thread.interrupted()) {
  17. // 实现代码在下行的注释中,设置相关参数来提醒其他线程周期失败了
  18. breakBarrier();
  19. /*
  20. * private void breakBarrier() {
  21. * generation.broken = true;
  22. * count = parties;
  23. * // 唤醒condition中的所有线程
  24. * trip.signalAll();
  25. * }
  26. */
  27. throw new InterruptedException();
  28. }
  29. // 如果成功了,那么剩余任务数(count)减1
  30. int index = --count;
  31. // 如果为0则表示达到剩余的任务数没有了,达到CyclicBarrier的条件总数了,需要唤醒其他线程
  32. if (index == 0) {
  33. boolean ranAction = false;
  34. try {
  35. // 唤醒之前的Runnable
  36. final Runnable command = barrierCommand;
  37. // 如果不为空的话执行其run方法
  38. if (command != null)
  39. command.run();
  40. ranAction = true;
  41. // 开启下个周期,这个方法是CyclicBarrier可以复用的原因,具体实现在下行注释
  42. nextGeneration();
  43. /* private void nextGeneration() {
  44. * // 首先叫醒当前周期的其他线程,告诉其周期结束了,可以执行接下来的操作了
  45. * trip.signalAll();
  46. * // 然后开启下个周期,剩余任务数重置
  47. * count = parties;
  48. * // 下个周期
  49. * generation = new Generation();
  50. * }
  51. */
  52. return 0;
  53. } finally {
  54. if (!ranAction)
  55. breakBarrier();
  56. }
  57. }
  58. // 如果还不能结束本周期,就一直等待直到结束或者周期失败
  59. for (;;) {
  60. try {
  61. // await的过程中是释放锁的
  62. // 不限时的话就一直等待直到被唤醒或者打断
  63. if (!timed)
  64. trip.await();
  65. else if (nanos > 0L)
  66. // 否则的话等待一段时间后醒来
  67. nanos = trip.awaitNanos(nanos);
  68. } catch (InterruptedException ie) {
  69. if (g == generation && ! g.broken) {
  70. breakBarrier();
  71. throw ie;
  72. } else {
  73. // We're about to finish waiting even if we had not
  74. // been interrupted, so this interrupt is deemed to
  75. // "belong" to subsequent execution.
  76. Thread.currentThread().interrupt();
  77. }
  78. }
  79. if (g.broken)
  80. throw new BrokenBarrierException();
  81. if (g != generation)
  82. return index;
  83. if (timed && nanos <= 0L) {
  84. breakBarrier();
  85. throw new TimeoutException();
  86. }
  87. }
  88. } finally {
  89. // 释放锁
  90. lock.unlock();
  91. }
  92. }

  到这里就基本理解CyclicBarrier的内部实现了,其他像带参数的await也是一样逻辑,只不过是多了限时的条件而已。

  其实如果你了解ReentrantLock的话,就知道CyclicBarrier整个就是对ReentrantLockcondition的活用而已。

3.总结

  整体来说CyclicBarrier的实现相对较简单,说是ReentrantLockcondition的升级版也不为过。其关键点为两个,一个为其构造函数,决定任务个数和唤醒前操作;另外一个点为await方法,在正常情况下每次await都会减少一个任务数(总数由构造方法决定),在任务数变为0的时候表示周期结束,需要唤醒condition的其他线程,而途中遇到失败的话当前周期失败,唤醒其他线程一起抛错。







失败不会让你变得弱小,害怕失败会。

CyclicBarrier是如何成为一个"栅栏"的的更多相关文章

  1. 并发编程(七)——AbstractQueuedSynchronizer 之 CountDownLatch、CyclicBarrier、Semaphore 源码分析

    这篇,我们的关注点是 AQS 最后的部分,共享模式的使用.本文先用 CountDownLatch 将共享模式说清楚,然后顺着把其他 AQS 相关的类 CyclicBarrier.Semaphore 的 ...

  2. CyclicBarrier 原理(秒懂)

    疯狂创客圈 经典图书 : <Netty Zookeeper Redis 高并发实战> 面试必备 + 面试必备 + 面试必备 [博客园总入口 ] 疯狂创客圈 经典图书 : <Sprin ...

  3. java 并发(五)---AbstractQueuedSynchronizer(3)

           文章代码分析和部分图片来自参考文章 问题 : CountDownLatch  和 CyclicBarrier 的区别 认识 CountDownLatch 分析这个类,首先了解一下它所可以 ...

  4. Java并发指南9:AQS共享模式与并发工具类的实现

    一行一行源码分析清楚 AbstractQueuedSynchronizer (三) 转自:https://javadoop.com/post/AbstractQueuedSynchronizer-3 ...

  5. 栅栏 CyclicBarrier

    java.util.concurrent.CyclicBarrier 类是一种同步机制,它能够对处理一些算法的线程实现同步.换句话讲,它就是一个所有线程必须等待的一个栅栏,直到所有线程都到达这里,然后 ...

  6. 戏说java多线程之CyclicBarrier(循环栅栏)的CyclicBarrier(int parties)构造方法

    CyclicBarrier是JDK 1.5 concurrent包出现的一个用于解决多条线程阻塞,当达到一定条件时一起放行的一个类.我们先来看这样一个简单的需求. 现在我有一个写入数据的类,继承Run ...

  7. Java并发编程原理与实战二十七:循环栅栏:CyclicBarrier

    昨天我们学习了倒计数功能的等待,今天我们学习的是循环栅栏:CyclicBarrier.下面我们就开始吧: 1.CyclicBarrier简介CyclicBarrier,是JDK1.5的java.uti ...

  8. 同步机制之--java CyclicBarrier 循环栅栏

    CyclicBarrier介绍一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point).在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待 ...

  9. 并发编程-concurrent指南-回环栅栏CyclicBarrier

    字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行. java.util.concurrent.CyclicBarrier 类是一种同步机制,它能够对处理一些算法的线程实现同步 ...

随机推荐

  1. 每日一点:git 与 github 区别

    絮絮叨叨在前:以前的公司,都用svn 进行代码管理.最近我那程序猿先生真的受不了我,强迫我使用tortoiseGit. 一开始对于 git 和 github 傻傻分不清,干脆自己整理资料,总结一下. ...

  2. angular -——组件样式修改不成功

    angular组件样式修改不成功! 自己定义的css可以成功 组件的不行 style在模板字符串里 直接没有 class 是显示的 但是样式不生效 加上面 即可,为什么?我也不太清楚.有知道答案的请回 ...

  3. 前端解决跨域问题的终极武器——Nginx反向代理

    提到代理,分为:正向代理和反向代理. 正向代理:就是你访问不了Google,但是国外有个VPN可以访问Google,你访问VPN后叫它访问Google,然后把数据传给你. 正向代理隐藏了真实的客户端. ...

  4. Eureka停更了?试试Zookpper和Consul

    在Spring Cloud Netflix中使用Eureak作为注册中心,但是Eureka2.0停止更新,Eureka1.0 进入了维护状态.就像win7一样,同样可以用,但是官方对于新出现的问题并不 ...

  5. 动态创建多个button

    2020-03-13 每日一例第6天 1.新建窗体windowform,修改text值: 2.找到mouseclick事件,填写代码: Random rm = new Random(); Button ...

  6. 原生JavaScript下的Ajax

    概述 AJAX即asynchronous javascript and XML,中文翻译是异步的javascript和XML.是指一种创建交互式网页应用.用于创建快速动态网页的开发技术. 传统的网页( ...

  7. (转)浅析epoll – epoll例子以及分析

    原文地址:http://www.cppfans.org/1419.html 浅析epoll – epoll例子以及分析 上篇我们讲到epoll的函数和性能.这一篇用用这些个函数,给出一个最简单的epo ...

  8. 解决使用 el-table 中使用多选框 Checkbox 不刷新问题

    问题 在 el-table 中使用 Checkbox 仅作为展示时,v-model 双向绑定就变得不那么适用了,这时候我们会使用 checked 属性来代替v-model. 问题来了当使用 filte ...

  9. 【简说Python WEB】Flask-Moment

    目录 [简说Python WEB]Flask-Moment 系统环境:Ubuntu 18.04.1 LTS Python使用的是虚拟环境:virutalenv Python的版本:Python 3.6 ...

  10. fatal: I don't handle protocol 'git@http' 解决

    新建的git,在git push的时候遇到了报错“fatal: I don't handle protocol 'git@http'” 网上搜这个错误基本都是“fatal: I don't handl ...