CyclicBarrier:人齐了,老司机就发车了!

开发 后端
CyclicBarrier 翻译为中文是循环(Cyclic)栅栏(Barrier)的意思,它的大概含义是实现一个可循环利用的屏障。

[[392061]]

上一篇咱讲了 CountDownLatch 可以解决多个线程同步的问题,相比于 join 来说它的应用范围更广,不仅可以应用在线程上,还可以应用在线程池上。然而 CountDownLatch 却是一次性的计数器,以王者农药来说,咱们不可能一场团战就决定比赛的输赢,所以在某些场景下,咱们是需要重复使用某个等待功能的,这就是我们今天要介绍的另一个主角——CyclicBarrier。

CyclicBarrier

CyclicBarrier 翻译为中文是循环(Cyclic)栅栏(Barrier)的意思,它的大概含义是实现一个可循环利用的屏障。

CyclicBarrier 作用是让一组线程相互等待,当达到一个共同点时,所有之前等待的线程再继续执行,且 CyclicBarrier 功能可重复使用。 å›¾ç‰‡

举个栗子

比如磊哥要坐班车回老家,因为中途不允许上、下乘客,所以营运的公司为了收益最大化,就会等人满之后再发车。像这种等人坐满就发一班车的场景,就是 CyclicBarrier 所擅长的,因为它可以重复使用(不像 CountDownLatch 那样只能用一次)。

[[392064]]

CyclicBarrier VS CountDownLatch

CountDownLatch:一个或者多个线程,等待另外 N 个线程完成某个事情之后才能执行。

CountDownLatch 就像玩王者农药开局的加载一样,所有人要等待其他人都加载 100% 之后才能开始游戏。

CyclicBrrier:N 个线程相互等待,直到有足够数量的线程都到达屏障点之后,之前等待的线程就可以继续执行了。

CyclicBrrier 就像老司机开车一样,如果车上还有空余的座位,那么所有人都得等着,直到座位被坐满之后,老司机才会发车。

 å›¾ç‰‡

CyclicBarrier使用

  1. import java.util.Date
  2. import java.util.Random; 
  3. import java.util.concurrent.*; 
  4.  
  5. public class CyclicBarrierExample { 
  6.     public static void main(String[] args) { 
  7.         // 创建 CyclicBarrier 
  8.         final CyclicBarrier cyclicBarrier = new CyclicBarrier(2, new Runnable() { 
  9.             @Override 
  10.             public void run() { 
  11.                 System.out.println("人满了,准备发车:" + new Date()); 
  12.             } 
  13.         }); 
  14.          
  15.         // 线程调用的任务 
  16.         Runnable runnable = new Runnable() { 
  17.             @Override 
  18.             public void run() { 
  19.                 // 生成随机数 1-3 
  20.                 int randomNumber = new Random().nextInt(3) + 1; 
  21.                 // 进入任务 
  22.                 System.out.println(String.format("我是:%s 再走:%d 秒就到车站了,现在时间:%s"
  23.                         Thread.currentThread().getName(), randomNumber, new Date())); 
  24.                 try { 
  25.                     // 模拟执行 
  26.                     TimeUnit.SECONDS.sleep(randomNumber); 
  27.                     // 调用 CyclicBarrier 
  28.                     cyclicBarrier.await(); 
  29.                     // 任务执行 
  30.                     System.out.println(String.format("线程:%s 上车,时间:%s"
  31.                             Thread.currentThread().getName(), new Date())); 
  32.                 } catch (InterruptedException e) { 
  33.                     e.printStackTrace(); 
  34.                 } catch (BrokenBarrierException e) { 
  35.                     e.printStackTrace(); 
  36.                 } 
  37.             } 
  38.         }; 
  39.  
  40.         // 创建线程池 
  41.         ExecutorService threadPool = Executors.newFixedThreadPool(10); 
  42.         // 执行任务 1 
  43.         threadPool.submit(runnable); 
  44.         // 执行任务 2 
  45.         threadPool.submit(runnable); 
  46.         // 执行任务 3 
  47.         threadPool.submit(runnable); 
  48.         // 执行任务 4 
  49.         threadPool.submit(runnable); 
  50.  
  51.         // 等待所有任务执行完终止线程池 
  52.         threadPool.shutdown(); 
  53.     } 

以上代码执行结果如下:

从上述结果可以看出:当 CyclicBarrier 的计数器设置为 2 时,线程 2 和 线程 3 都到屏障点之后,老司机才会发第一波车,再 2s 之后,线程 1 和线程 4 也同时进入了屏障点,这时候老司机又可以再发一波车了。

实现原理

我们先来看下 CyclicBarrier 的类图:

由上图可知 CyclicBarrier 是基于独占锁 ReentrantLock 实现的,其底层也是基于 AQS 的。

在 CyclicBarrier 类的内部有一个计数器 count,当 count 不为 0 时,每个线程在到达屏障点会先调用 await 方法将自己阻塞,此时计数器会减 1,直到计数器减为 0 的时候,所有因调用 await 方法而被阻塞的线程就会被唤醒继续执行。当 count 计数器变成 0 之后,就会进入下一轮阻塞,此时 parties(parties 是在 new CyclicBarrier(parties) 时设置的值)会将它的值赋值给 count 从而实现复用。

常用方法

CyclicBarrier(parties):初始化相互等待的线程数量的构造方法。

CyclicBarrier(parties,Runnable barrierAction):初始化相互等待的线程数量以及屏障线程的构造方法,当 CyclicBarrier 的计数器变为 0 时,会执行 barrierAction 构造方法。

getParties():获取 CyclicBarrier 打开屏障的线程数量,也称为方数。

getNumberWaiting():获取正在CyclicBarrier上等待的线程数量。

await():在 CyclicBarrier 上进行阻塞等待,直到发生以下情形之一:在 CyclicBarrier 上等待的线程数量达到 parties,则所有线程被释放,继续执行;

  • 当前线程被中断,则抛出 InterruptedException 异常,并停止等待,继续执行;
  • 其他等待的线程被中断,则当前线程抛出 BrokenBarrierException 异常,并停止等待,继续执行;
  • 其他等待的线程超时,则当前线程抛出 BrokenBarrierException 异常,并停止等待,继续执行;
  • 其他线程调用 CyclicBarrier.reset() 方法,则当前线程抛出 BrokenBarrierException 异常,并停止等待,继续执行。

await(timeout,TimeUnit):在CyclicBarrier上进行限时的阻塞等待,直到发生以下情形之一:

  • 在 CyclicBarrier 上等待的线程数量达到 parties,则所有线程被释放,继续执行;
  • 当前线程被中断,则抛出 InterruptedException 异常,并停止等待,继续执行;
  • 当前线程等待超时,则抛出 TimeoutException 异常,并停止等待,继续执行;
  • 其他等待的线程被中断,则当前线程抛出 BrokenBarrierException 异常,并停止等待,继续执行;
  • 其他等待的线程超时,则当前线程抛出 BrokenBarrierException 异常,并停止等待,继续执行;
  • 其他线程调用 CyclicBarrier.reset() 方法,则当前线程抛出 BrokenBarrierException 异常,并停止等待,继续执行。

isBroken():获取是否破损标志位 broken 的值,此值有以下几种情况:

  • CyclicBarrier 初始化时,broken=false,表示屏障未破损;
  • 如果正在等待的线程被中断,则 broken=true,表示屏障破损;
  • 如果正在等待的线程超时,则 broken=true,表示屏障破损;
  • 如果有线程调用 CyclicBarrier.reset() 方法,则 broken=false,表示屏障回到未破损状态。

reset():使得CyclicBarrier回归初始状态,直观来看它做了两件事:

  • 如果有正在等待的线程,则会抛出 BrokenBarrierException 异常,且这些线程停止等待,继续执行。
  • 将是否破损标志位 broken 置为 false。

总结

CyclicBrrier 是通过独占锁 ReentrantLock 实现计数器的原子性更新的,CyclicBrrier 最常用的是 await() 方法,使用此方法会将计数器 -1,并判断当前的计数器是否为 0,如果不为 0 就会阻塞等待,并计时器为 0 之后,才能继续执行剩余任务。CyclicBrrier 相比于 CountDownLatch 来说,它的优势在于可以重复使用。

参考 & 鸣谢

  • blog.csdn.net/qq_39241239/article/details/87030142
  • blog.csdn.net/zzg1229059735/article/details/61191679
  • www.cnblogs.com/yaochunhui/p/13494689.html

 

责任编辑:姜华 来源: Java中文社群
相关推荐

2016-10-10 08:38:40

Windows 10备份格式化

2022-01-11 16:54:16

异步编程方式

2018-12-19 10:52:35

嵌入式CPU微处理器

2017-07-17 17:13:58

老司机

2018-01-15 10:51:42

2018-06-01 16:37:05

果然是老司机,效率实在

2016-11-07 12:30:59

大数据互联网

2019-12-17 09:29:02

数据库架构分库分表

2019-08-12 10:27:34

前端程序员网络

2019-04-28 11:59:24

Wi-Fi 6Wi-Fi网络

2017-09-20 09:06:20

程序员网站后端服务

2018-03-28 12:33:21

滴滴高德地图平台

2010-03-04 17:19:17

Android核心系统

2019-03-25 07:14:57

程序员工程师职业

2021-05-07 06:15:32

编程开发端口扫描

2023-09-15 06:59:00

AMD 32处理器撕裂者

2020-11-09 14:15:23

代码菜鸟老司机

2017-05-24 10:58:28

linux系统技巧

2024-06-17 07:30:00

2017-07-10 16:19:36

IT发财离婚
点赞
收藏

51CTO技术栈公众号