一、CyclicBarrier使用

CyclicBarrier从字面上可以直接理解为线程运行的屏障,它可以让一组线程执行到一个共同的屏障点时被阻塞,直到最后一个线程执行到指定位置,你设置的执行线程就会触发运行;同时CyclicBarrier相比与CountDownLatch,它是可以被重置的;下面我们通过一个简单例子看下CyclicBarrier的使用;

实例化一个CyclicBarrier对象并传入你要控制的线程内部;

  1. public static void main(String[] args) {
  2.  
  3. CyclicBarrier cb = new CyclicBarrier(3, new Runnable() {
  4. public void run() {
  5. System.out.println("所有线程集合");
  6. }
  7. });
  8. for (int i = 0; i < 3; i++) {
  9. new CyclicBarrierThread(i + "", cb).start();
  10. }
  11.  
  12. }

计数线程代码,每当计数到偶数时调用CyclicBarrier的await()方法

  1. public class CyclicBarrierThread extends Thread{
  2.  
  3. private CyclicBarrier barrier;
  4.  
  5. private String name;
  6.  
  7. private int count;
  8.  
  9. public CyclicBarrierThread(String name,CyclicBarrier barrier) {
  10. this.name=name;
  11. this.barrier=barrier;
  12. this.count=0;
  13. }
  14.  
  15. public void run() {
  16. try {
  17. for(int i=0;i<10;i++) {
  18.  
  19. Thread.sleep(100);
  20. count++;
  21. System.out.println(name+"号线程---"+Thread.currentThread().getName()+"开始计数:"+count);
  22. if(count%2==0) {//每计数到偶数次时集合一次
  23. barrier.await();
  24. System.out.println(name+"号线程---"+Thread.currentThread().getName()+"集合完毕,继续计数");
  25. }
  26. }
  27.  
  28. } catch (Exception e) {
  29. // TODO Auto-generated catch block
  30. e.printStackTrace();
  31. }
  32.  
  33. }
  34. }

查看代码输出

  1. 2号线程---Thread-2开始计数:1
  2. 0号线程---Thread-0开始计数:1
  3. 1号线程---Thread-1开始计数:1
  4. 2号线程---Thread-2开始计数:2
  5. 1号线程---Thread-1开始计数:2
  6. 0号线程---Thread-0开始计数:2
  7. 所有线程集合
  8. 2号线程---Thread-2集合完毕,继续计数
  9. 1号线程---Thread-1集合完毕,继续计数
  10. 0号线程---Thread-0集合完毕,继续计数
  11. 2号线程---Thread-2开始计数:3
  12. 1号线程---Thread-1开始计数:3
  13. 0号线程---Thread-0开始计数:3
  14. 2号线程---Thread-2开始计数:4
  15. 0号线程---Thread-0开始计数:4
  16. 1号线程---Thread-1开始计数:4
  17. 所有线程集合
  18. 1号线程---Thread-1集合完毕,继续计数
  19. 2号线程---Thread-2集合完毕,继续计数
  20. 0号线程---Thread-0集合完毕,继续计数
  21. 0号线程---Thread-0开始计数:5
  22. 2号线程---Thread-2开始计数:5
  23. 1号线程---Thread-1开始计数:5
  24. 0号线程---Thread-0开始计数:6
  25. 1号线程---Thread-1开始计数:6
  26. 2号线程---Thread-2开始计数:6
  27. 所有线程集合
  28. 2号线程---Thread-2集合完毕,继续计数
  29. 0号线程---Thread-0集合完毕,继续计数
  30. 1号线程---Thread-1集合完毕,继续计数
  31. 0号线程---Thread-0开始计数:7
  32. 1号线程---Thread-1开始计数:7
  33. 2号线程---Thread-2开始计数:7
  34. 1号线程---Thread-1开始计数:8
  35. 0号线程---Thread-0开始计数:8
  36. 2号线程---Thread-2开始计数:8
  37. 所有线程集合
  38. 2号线程---Thread-2集合完毕,继续计数
  39. 0号线程---Thread-0集合完毕,继续计数
  40. 1号线程---Thread-1集合完毕,继续计数
  41. 0号线程---Thread-0开始计数:9
  42. 1号线程---Thread-1开始计数:9
  43. 2号线程---Thread-2开始计数:9
  44. 1号线程---Thread-1开始计数:10
  45. 0号线程---Thread-0开始计数:10
  46. 2号线程---Thread-2开始计数:10
  47. 所有线程集合
  48. 1号线程---Thread-1集合完毕,继续计数
  49. 2号线程---Thread-2集合完毕,继续计数
  50. 0号线程---Thread-0集合完毕,继续计数

通过输出结果可以看到,计数线程每计数到偶数次时使用CyclicBarrier的await()方法,线程都会进入阻塞等待的状态,直到最后一个线程到达屏障点时,触发你定义的执行线程,而且CyclicBarrier的await()方法是可以重复使用的。

二、CyclicBarrier源码分析

下面我们就对CyclicBarrier内部的源码实现进行一些分析与总结

1、CyclicBarrier的构造

首先看下CyclicBarrier的构造函数

  1. public CyclicBarrier(int parties, Runnable barrierAction) {
  2. if (parties <= 0) throw new IllegalArgumentException();
  3. //拦截的线程数量
  4. this.parties = parties;
  5. //用于计数的count值,每有一个线程执行到屏障点,就会递减1
  6. this.count = parties;
  7. //定义的拦截线程
  8. this.barrierCommand = barrierAction;
  9. }

CyclicBarrier的构造函数很简单就是接收你要拦截的线程数量与定义的执行线程。

2、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方法的实现

  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. //CyclicBarrier内部定义的一个Generation类
  10. final Generation g = generation;
  11.  
  12. //判断Generation的broken状态
  13. if (g.broken)
  14. throw new BrokenBarrierException();
  15.  
  16. //如果线程被中断
  17. if (Thread.interrupted()) {
  18. //Generation的broken置为true,count值重置,并唤醒所有线程
  19. breakBarrier();
  20. throw new InterruptedException();
  21. }
  22.  
  23. //count值减一
  24. int index = --count;
  25. if (index == 0) { // 如果conunt为0,说明最后一个线程到大屏障
  26. boolean ranAction = false;
  27. try {
  28. final Runnable command = barrierCommand;
  29. if (command != null)
  30. command.run();//执行你传入的线程
  31. ranAction = true;
  32. nextGeneration();//唤醒所有阻塞的线程,同时重置count值与Generation
  33. return 0;
  34. } finally {
  35. if (!ranAction)
  36. //拦截线程没有正常执行,唤醒所有线程,同时重置count值,Generation的broken置为true
  37. breakBarrier();
  38. }
  39. }
  40.  
  41. // loop until tripped, broken, interrupted, or timed out
  42. for (;;) {
  43. try {
  44. //是否设置阻塞的超时时间
  45. if (!timed)
  46. //释放当前锁
  47. trip.await();//false 表示不设置,一直阻塞
  48. else if (nanos > 0L)
  49. nanos = trip.awaitNanos(nanos);//true 设置阻塞的超时时间
  50. } catch (InterruptedException ie) {
  51. if (g == generation && ! g.broken) {
  52. breakBarrier();
  53. throw ie;
  54. } else {
  55. // We're about to finish waiting even if we had not
  56. // been interrupted, so this interrupt is deemed to
  57. // "belong" to subsequent execution.
  58. Thread.currentThread().interrupt();
  59. }
  60. }
  61.  
  62. if (g.broken)
  63. throw new BrokenBarrierException();
  64.  
  65. if (g != generation)
  66. return index;
  67.  
  68. if (timed && nanos <= 0L) {
  69. breakBarrier();
  70. throw new TimeoutException();
  71. }
  72. }
  73. } finally {
  74. //释放锁
  75. lock.unlock();
  76. }
  77. }

dowait方法的实现流程是很清晰的,通过ReentrantLock的Condition接口与count值相互配合,主要完成以下功能:

1、当需要拦截的线程到达屏障点调用await方法后获取ReentrantLock锁,保证线程安全;

2、检查count值是否为0,判断是否是最后一个线程到达屏障,如果是的话执行需要触发执行的线程,调用Condition的signalAll方法唤醒所有阻塞的线程,并重置count值与Generation类,保障CyclicBarrier的重复可用;

3、如果不是最后一个线程的话,根据传入的参数调用Condition的await方法释放锁资源并进入阻塞等待,直到被唤醒;

3、reset方法

可以用来主动重置CyclicBarrier的状态

  1. public void reset() {
  2. final ReentrantLock lock = this.lock;
  3. lock.lock();
  4. try {
  5. //generation.broken设置为true,唤醒所有线程,count值重置
  6. breakBarrier();
  7. nextGeneration();
  8. } finally {
  9. lock.unlock();
  10. }
  11. }
  12.  
  13. private void nextGeneration() {
  14. // signal completion of last generation
  15. trip.signalAll();
  16. // set up next generation
  17. count = parties;
  18. generation = new Generation();
  19. }
  20.  
  21. private void breakBarrier() {
  22. generation.broken = true;
  23. count = parties;
  24. trip.signalAll();
  25. }

breakBarrier()与nextGeneration(),这两个方法的主要区别就在于前者会把generation.broken设置为true,也就是说如果调用reset方法主动重置CyclicBarrier类的状态,当前正在使用CyclicBarrier类同步的线程都会被唤醒或抛出异常;

4、getNumberWaiting方法

  1. public int getNumberWaiting() {
  2. final ReentrantLock lock = this.lock;
  3. lock.lock();
  4. try {
  5. return parties - count;
  6. } finally {
  7. lock.unlock();
  8. }
  9. }

很明显getNumberWaiting方法使用来获取当前已经运行至屏蔽点并阻塞等待的线程数量的;

三、总结

通过上面分析可以看到CyclicBarrier的实现原理相对还是比较简单与清晰的,主要是基于ReentrantLock与计数器相结合来实现多个线程的同步控制的。以上就是对CyclicBarrier类的使用与内部实现进行的分析,其中如有不足与不正确的地方还望指出与海涵。

关注微信公众号,查看更多技术文章。

Java多线程同步工具类之CyclicBarrier的更多相关文章

  1. Java多线程同步工具类之CountDownLatch

    在过去我们实现多线程同步的代码中,往往使用join().wait().notiyAll()等线程间通信的方式,随着JUC包的不断的完善,java为我们提供了丰富同步工具类,官方也鼓励我们使用工具类来实 ...

  2. Java多线程同步工具类之Semaphore

    Semaphore信号量通常做为控制线程并发个数的工具来使用,它可以用来限制同时并发访问资源的线程个数. 一.Semaphore使用 下面我们通过一个简单的例子来看下Semaphore的具体使用,我们 ...

  3. Java多线程并发工具类-信号量Semaphore对象讲解

    Java多线程并发工具类-Semaphore对象讲解 通过前面的学习,我们已经知道了Java多线程并发场景中使用比较多的两个工具类:做加法的CycliBarrier对象以及做减法的CountDownL ...

  4. Java并发编程工具类 CountDownLatch CyclicBarrier Semaphore使用Demo

    Java并发编程工具类 CountDownLatch CyclicBarrier Semaphore使用Demo CountDownLatch countDownLatch这个类使一个线程等待其他线程 ...

  5. java 利用同步工具类控制线程

    前言 参考来源:<java并发编程实战> 同步工具类:根据工具类的自身状态来协调线程的控制流.通过同步工具类,来协调线程之间的行为. 可见性:在多线程环境下,当某个属性被其他线程修改后,其 ...

  6. Java多线程并发工具类

    Semaphore-信号灯机制 当我们创建一个可扩展大小的线程池,并且需要在线程池内同时让有限数目的线程并发运行时,就需要用到Semaphore(信号灯机制),Semaphore 通常用于限制可以访问 ...

  7. Java多线程——其他工具类CyclicBarrier、CountDownLatch和Exchange

    CyclicBarrier 适用于:创建一组任务,它们并行地执行任务,然后在进行下一个步骤之前等待,直至所有任务完成.它使得所有的并行任务都将在栅栏处列队,因此可以一致地向前移动. 表示大家彼此等待, ...

  8. 并发包下常见的同步工具类(CountDownLatch,CyclicBarrier,Semaphore)

    在实际开发中,碰上CPU密集且执行时间非常耗时的任务,通常我们会选择将该任务进行分割,以多线程方式同时执行若干个子任务,等这些子任务都执行完后再将所得的结果进行合并.这正是著名的map-reduce思 ...

  9. Java并发——同步工具类

    CountDownLatch  同步倒数计数器 CountDownLatch是一个同步倒数计数器.CountDownLatch允许一个或多个线程等待其他线程完成操作. CountDownLatch对象 ...

随机推荐

  1. WPF中的Generic.xaml, theme以及custom control

    原文:WPF中的Generic.xaml, theme以及custom control 在Visual Studio中创建自定义控件时,所有控件都将添加到/Themes/Generic.xaml. 最 ...

  2. wpf控件开发基础(4) -属性系统(3)

    原文:wpf控件开发基础(4) -属性系统(3) 知识回顾 接上篇,上篇我们真正接触到了依赖属性的用法,以及依赖属性的属性元数据的用法,并且也实实在在地解决了之前第二篇提到的一系列问题.来回顾一下 属 ...

  3. python 两个链表的第一个公共结点

    题目描述 输入两个链表,找出它们的第一个公共结点.   看到这道题的时候,很多人的第一反应就是采用蛮力的方法:在第一个链表上顺序遍历每个节点,每遍历到一个节点的时候,在第二个链表上顺序遍历每个节点.如 ...

  4. 修复Windows 10 SDK 17763中NavigationView上的AcrylicBrush丢失

    原文 修复Windows 10 SDK 17763中NavigationView上的AcrylicBrush丢失 Microsoft发布了新版本的Windows 10 UWP SDK Build 17 ...

  5. Bootstrap Thumbnail

    Square Thumbnail with Image <!-- Square Thumbnail with Image --> <com.beardedhen.androidboo ...

  6. 本文摘录 - FlumeJava

    本文节选不保证论文的完整性和理解的准确性  原始的MapReduce.分Map,Shuffle,Reduce. Map里包含shards. Shuffle理解为groupByKey的事情.Reduce ...

  7. cat监控平台环境搭建 专题

    项目地址:https://github.com/dianping/cat 编译步骤: 这个项目比较另类,把编译需要的jar包,单独放在git分支mvn-repo里了,而且官方文档里给了一个错误的命令提 ...

  8. INCORRECT PERMISSIONS ON /USR/LIB/PO1KIT-AGENT-HELPER-1(NEEDS TO BE SETUID ROOT)

    INCORRECT PERMISSIONS ON /USR/LIB/PO1KIT-AGENT-HELPER-1(NEEDS TO BE SETUID ROOT) # sudo chmod +s /us ...

  9. 漫谈 JVM —— 内存

    JVM 是什么呢?说的直白点就是 Java 代码运行的地方,全称 Java Virtural Machine,Java 虚拟机.有的人就会奇怪了,为什么 Java 程序员需要了解这个东西?毕竟大多数情 ...

  10. Selenium-等待

    分为3种 (1)就是通过线程强制等待 Thread.sleep(1000); (2)隐示等待.就是所有的命令都等待.分为3种 // 这个方法表示全局的等待.意思是针对所有的findElement方法都 ...