上一篇说的CountDownLatch是一个计数器,类似线程的join方法,但是有一个缺陷,就是当计数器的值到达0之后,再调用CountDownLatch的await和countDown方法就会立刻返回,就没有作用了,那么反正是一个计数器,为什么不能重复使用呢?于是就出现了这篇说的CyclicBarrier,它的状态可以被重用;

一.简单例子

  用法其实和CountDownLatch差不多,也就是一个计数器,当计数器的值变为0之后,就会把阻塞的线程唤醒:

  1. package com.example.demo.study;
  2.  
  3. import java.util.concurrent.CyclicBarrier;
  4. import java.util.concurrent.ExecutorService;
  5. import java.util.concurrent.Executors;
  6.  
  7. public class Study0216 {
  8. // 注意这里的构造器,第一个参数表示计数器初始值
  9. // 第二个参数表示当计数器的值变为0的时候就触发的任务
  10. static CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> {
  11. System.out.println("cyclicBarrier task ");
  12. });
  13.  
  14. public static void main(String[] args) {
  15. // 新建两个线程的线程池
  16. ExecutorService pool = Executors.newFixedThreadPool(2);
  17. // 线程1放入线程池中
  18. pool.submit(() -> {
  19. try {
  20. System.out.println("Thread1----await-begin");
  21. cyclicBarrier.await();
  22. System.out.println("Thread1----await-end");
  23. } catch (Exception e) {
  24. e.printStackTrace();
  25. }
  26. });
  27. // 线程2放到线程池中
  28. pool.submit(() -> {
  29. try {
  30. System.out.println("Thread2----await-begin");
  31. cyclicBarrier.await();
  32. System.out.println("Thread2----await-end");
  33. } catch (Exception e) {
  34. e.printStackTrace();
  35. }
  36. });
  37. // 关闭线程池,此时还在执行的任务会继续执行
  38. pool.shutdown();
  39. }
  40. }

  我们再看看CyclicBarrier的复用性,这里比如有一个任务,有三部分组成,分别是A,B,C,然后创建两个线程去执行这个任务,必须要等到两个线程都执行完成A部分,然后才能开始执行B,只有两个线程都执行完成B部分,才能执行C:

  1. package com.example.demo.study;
  2.  
  3. import java.util.concurrent.CyclicBarrier;
  4. import java.util.concurrent.ExecutorService;
  5. import java.util.concurrent.Executors;
  6.  
  7. public class Study0216 {
  8. // 这里的构造器,只有一个参数,表示计数器初始值
  9. static CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
  10.  
  11. public static void main(String[] args) {
  12. // 新建两个线程的线程池
  13. ExecutorService pool = Executors.newFixedThreadPool(2);
  14. // 线程1放入线程池中
  15. pool.submit(() -> {
  16. try {
  17. System.out.println("Thread1----stepA-start");
  18. cyclicBarrier.await();
  19.  
  20. System.out.println("Thread1----stepB-start");
  21. cyclicBarrier.await();
  22.  
  23. System.out.println("Thread1----stepC-start");
  24.  
  25. } catch (Exception e) {
  26. e.printStackTrace();
  27. }
  28. });
  29. // 线程2放到线程池中
  30. pool.submit(() -> {
  31. try {
  32. System.out.println("Thread2----stepA-start");
  33. cyclicBarrier.await();
  34.  
  35. System.out.println("Thread2----stepB-start");
  36. cyclicBarrier.await();
  37.  
  38. System.out.println("Thread2----stepC-start");
  39. } catch (Exception e) {
  40. e.printStackTrace();
  41. }
  42. });
  43. // 关闭线程池,此时还在执行的任务会继续执行
  44. pool.shutdown();
  45. }
  46. }

二.基本原理

  我们看看一些重要属性:

  1. public class CyclicBarrier {
  2. //这个内部类只有一个boolean值
  3. private static class Generation {
  4. boolean broken = false;
  5. }
  6.  
  7. //独占锁
  8. private final ReentrantLock lock = new ReentrantLock();
  9. //条件变量
  10. private final Condition trip = lock.newCondition();
  11. //保存线程的总数
  12. private final int parties;
  13. //这是一个任务,通过构造器传递一个任务,当计数器变为0之后,就可以执行这个任务
  14. private final Runnable barrierCommand;
  15. //这类内部之后一个boolean的值,表示屏障是否被打破
  16. private Generation generation = new Generation();
  17. //计数器
  18. private int count;
  19. }

  构造器:

  1. //我们的构造器初始值设置的是parties
  2. public CyclicBarrier(int parties) {
  3. this(parties, null);
  4. }
  5. //注意,这里开始的时候是count等于parties
  6. //为什么要有两个变量呢?我们每次调用await方法的时候count减一,当count的值变为0之后,怎么又还原成初始值呢?
  7. //直接就把parties的值赋值给count就行了呀,简单吧!
  8. public CyclicBarrier(int parties, Runnable barrierAction) {
  9. if (parties <= 0) throw new IllegalArgumentException();
  10. this.parties = parties;
  11. this.count = parties;
  12. this.barrierCommand = barrierAction;
  13. }

  然后再看看await方法:

  1. public int await() throws InterruptedException, BrokenBarrierException {
  2. try {
  3. //调用的是dowait方法
  4. return dowait(false, 0L);
  5. } catch (TimeoutException toe) {
  6. throw new Error(toe); // cannot happen
  7. }
  8. }
  9.  
  10. //假设count等于3,有三个线程都在调用这个方法,默认超时时间为0,那么首每次都只有一个线程可以获取锁,将count减一,不为0
  11. //就会到下面的for循环中扔到条件队列中挂起;直到第三个线程调用这个dowait方法,count减一等于0,那么当前线程执行任务之后,
  12. //就会唤醒条件变量中阻塞的线程,并重置count为初始值3
  13. private int dowait(boolean timed, long nanos)throws InterruptedException, BrokenBarrierException, TimeoutException {
  14. //获取锁
  15. final ReentrantLock lock = this.lock;
  16. lock.lock();
  17. try {
  18. //g中只有一个boolean值
  19. final Generation g = generation;
  20. //如果g中的值为true的时候,抛错
  21. if (g.broken)
  22. throw new BrokenBarrierException();
  23. //如果当前线程中断,就抛错
  24. if (Thread.interrupted()) {
  25. breakBarrier();
  26. throw new InterruptedException();
  27. }
  28. //count减一,再赋值给index
  29. int index = --count;
  30. //如果index等于0的时候,说明所有的线程已经到屏障点了,就可以
  31. if (index == 0) { // tripped
  32. boolean ranAction = false;
  33. try {
  34. //执行当前线程的任务
  35. final Runnable command = barrierCommand;
  36. if (command != null)
  37. command.run();
  38. ranAction = true;
  39. //唤醒其他因为调用了await方法阻塞的线程
  40. nextGeneration();
  41. return 0;
  42. } finally {
  43. if (!ranAction)
  44. breakBarrier();
  45. }
  46. }
  47. //能到这里来,说明是count不等于0,也就是还有的线程没有到屏障点
  48. for (;;) {
  49. try {
  50. //wait方法有两种情况,一种是设置超时时间,一种是不设置超时时间
  51. //这里就是对超时时间进行的一个判断,如果设置的超时时间为0,则会在条件队列中无限的等待下去,直到被唤醒
  52. //设置了超时时间,那就等待该时间
  53. if (!timed)
  54. trip.await();
  55. else if (nanos > 0L)
  56. nanos = trip.awaitNanos(nanos);
  57. } catch (InterruptedException ie) {
  58. if (g == generation && ! g.broken) {
  59. breakBarrier();
  60. throw ie;
  61. } else {
  62. Thread.currentThread().interrupt();
  63. }
  64. }
  65.  
  66. if (g.broken)
  67. throw new BrokenBarrierException();
  68.  
  69. if (g != generation)
  70. return index;
  71.  
  72. if (timed && nanos <= 0L) {
  73. breakBarrier();
  74. throw new TimeoutException();
  75. }
  76. }
  77. } finally {
  78. //释放锁
  79. lock.unlock();
  80. }
  81. }
  82.  
  83. //唤醒其他因为调用了await方法阻塞的线程
  84. private void nextGeneration() {
  85. //唤醒条件变量中所有线程
  86. trip.signalAll();
  87. //重置count的值
  88. count = parties;
  89. generation = new Generation();
  90. }
  91.  
  92. private void breakBarrier() {
  93. generation.broken = true;
  94. //重置count为初始值parties
  95. count = parties;
  96. //唤醒条件队列中的所有线程
  97. trip.signalAll();
  98. }

回环屏障CyclicBarrier的更多相关文章

  1. CyclicBarrier回环屏障深度解析

    1. 前沿 从上一节的CountDownLatch的学习,我们发现其只能使用一次,当state递减为0后,就没有用了,需要重新新建一个计数器.那么我们有没有可以复用的计数器呢?当然,JUC包给我们提供 ...

  2. 回环栅栏CyclicBarrier

    通过它可以实现让一组线程等待至某个状态之后再全部同时执行.叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用.我们暂且把这个状态就叫做barrier,当调用await()方 ...

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

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

  4. thread_CyclicBarrier回环栅栏

    CyclicBarrier回环栅栏,字面意思是可循环使用(Cyclic)的屏障(Barrier).通过它可以实现让一组线程等待至某个状态之后再全部同时执行. 它要做的事情是,让一组线程到达一个屏障(也 ...

  5. ORB-SLAM(六)回环检测

    上一篇提到,无论在单目.双目还是RGBD中,追踪得到的位姿都是有误差的.随着路径的不断延伸,前面帧的误差会一直传递到后面去,导致最后一帧的位姿在世界坐标系里的误差有可能非常大.除了利用优化方法在局部和 ...

  6. SharePoint回环检查(Loopback Check)相关问题

    Loopback Check(回环检查)本来不是一个SharePoint问题,是Windows Server为了增强自身安全性在Server 2003 SP1后引入的一个功能, 在近几个月中导致了一系 ...

  7. 关于STM32 CAN回环可用,正常不可用情况分析

    1.回环下应该与GPIO无关 2.GPIO是否初始化正确,时钟启用 3.是否复用,AFIO时钟是否启用 4.回环下是否有CAN_Tx应该有输出 5.终端电阻是否有 6.CAN收发器电路电压是否正常 7 ...

  8. linux回环网卡驱动设计

    回环网卡驱动 1.回环网卡和普通网卡的区别是他是虚拟的不是实际的物理网卡,它相当于把普通网卡的发送端和接收端短接在一起. 2.在内核源代码里的回环网卡程序(drivers/net/loopback.c ...

  9. VMware配置回环地址用于测试

           我们在开发过程中,很可能需要一台服务器用于测试,在这种环境下,我们很可能需要用到vmware来构建这样的开发环境.但如果当前处在一个离线,或是不在网内的环境下,我们所搭建的环境有可能无法 ...

随机推荐

  1. mysql 主主备份

    1.1.主主备份原理. 主主备份实际上是互为主从,主要是为了去缓解写入压力. 1.2.环境准备 两台机器ip分别为 100.100.100.105 (主1) 100.100.100.106(主2) 安 ...

  2. SpringBoot图文教程6—SpringBoot中过滤器的使用

    有天上飞的概念,就要有落地的实现 概念十遍不如代码一遍,朋友,希望你把文中所有的代码案例都敲一遍 先赞后看,养成习惯 SpringBoot 图文系列教程技术大纲 鹿老师的Java笔记 SpringBo ...

  3. python数据类型(第一弹)

    作为一门计算机编程语言,python与其它语言一样,设有若干种数据类型,准确掌握各种数据类型的常用方法是精通python的必要条件,也是熟练使用各数据类型.最大限度发挥它们功能的基本条件. pytho ...

  4. js中的节点遍历+类数组对象

    firstChild  第一个子元素 lastChild  最后一个子元素 childNodes[n]  =   childNodes.item(n)    第n+1个子元素 parentNode  ...

  5. PP: Time series anomaly detection with variational autoencoders

    Problem: unsupervised anomaly detection Model: VAE-reEncoder VAE with two encoders and one decoder. ...

  6. 手写MyBatis流程

    MyBatis 手写MyBatis流程 架构流程图 封装数据 封装到Configuration中 1.封装全局配置文件,包含数据库连接信息和mappers信息 2.封装*mapper.xml映射文件 ...

  7. 剑指offer-面试题29-顺时针打印矩阵-矩阵

    /* 题目: 输入一个矩阵,按照从外到内顺时针的顺序依次打印每一个数字. */ /* 思路: 1.将打印矩阵看作是打印一个个从外向内的环. 2.每一个环都有一个起始节点,起始节点的坐标*2小于行数和列 ...

  8. json转dataset的另外一种解析方式自动生成guid强关联

    /// <summary> /// 将json字符串自动转成dataset,并且自动补全主子关联关系, /// Guid,FKGuid /// Author:lijia /// date: ...

  9. (C语言)学生成绩排序-期末考倒数第二题结构体数组排序

    假设学生的基本信息包括学号.姓名.三门课程成绩以及个人平均成绩,定义一个能够表示学生信息的结构类型.输入n(n<50)个学生的成绩信息,按照学生的个人平均分从高到低输出他们的信息.如果平均分相同 ...

  10. python3练习100题——040

    原题链接:http://www.runoob.com/python/python-exercise-example40.html 题目:将一个数组逆序输出. a=[1,2,3,4,5] print a ...