面试官:说一说CyclicBarrier的妙用!我:这个没用过

写在开头

面试官:同学,AQS的原理知道吗?
我:学过一点,抽象队列同步器,Java中很多同步工具都是基于它的...
面试官:好的,那其中CyclicBarrier学过吗?讲一讲它的妙用吧
我:啊,这个,这个我平时写代码没用过...
面试官:那你回去再学学吧!

随着Java的国内竞争环境逐渐激烈,面试时遇到很多奇葩的问题也是越来越多,以上是模拟的一个面试场景,同学们看下你们能答得上来不?😝

什么是CyclicBarrier?

在过去的几天里,我们基于AQS学习了不少内容,其中基于AQS构建的同步工具类也学了Semaphore(信号量)和CountDownLatch(倒计时器),甚至于也手撕过同步器,今天我们继续来学习另外一个同步类:CyclicBarrier

CyclicBarrier(循环屏障):让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。

CyclicBarrier的原理

在CyclicBarrier有两个成员变量分别为partiescount,前者代表每次拦截的线程数量,后者是初始化时保持和parties相等的计数标识,每有一个线程执行到同步点时,count减1,当count值变为0时说明所有线程都走到了同步点,这时就可以尝试执行我们在构造方法中设计的任务啦。

【源码解析1】

//每次拦截的线程数 private final int parties; //计数器 private int count;  //一个参数的构造 public CyclicBarrier(int parties) {     this(parties, null); } //多参构造,parties为拦截线程数,barrierAction这个 Runnable会在 CyclicBarrier 的计数器为 0 的时候执行,用来完成更复杂的任务。 public CyclicBarrier(int parties, Runnable barrierAction) {     if (parties <= 0) throw new IllegalArgumentException();     this.parties = parties;     this.count = parties;     this.barrierCommand = barrierAction; } 

每个线程通过调用await方法告诉CyclicBarrier已经到达屏障,然后进行阻塞等待,知道count等于0,所有线程都到达了屏障,因此,我们跟入await方法的源码中去看一下。

【源码解析2】

public int await() throws InterruptedException, BrokenBarrierException {   try {       return dowait(false, 0L);   } catch (TimeoutException toe) {       throw new Error(toe); // cannot happen   } } //await方法内部,继续调用dowait方法实现功能 private int dowait(boolean timed, long nanos)     throws InterruptedException, BrokenBarrierException,            TimeoutException {     final ReentrantLock lock = this.lock;     // 锁住     lock.lock();     try {         final Generation g = generation;         if (g.broken)             throw new BrokenBarrierException();          // 如果线程中断了,抛出异常         if (Thread.interrupted()) {        		//打破屏障             breakBarrier();             throw new InterruptedException();         }         // cout减1         int index = --count;         // 当 count 数量减为 0 之后说明最后一个线程已经到达栅栏了,也就是达到了可以执行await 方法之后的条件         if (index == 0) {  // tripped             boolean ranAction = false;             try {                 final Runnable command = barrierCommand;                 if (command != null)                     command.run();                 ranAction = true;                 // 将 count 重置为 parties 属性的初始化值                 // 唤醒之前等待的线程                 // 下一波执行开始                 nextGeneration();                 return 0;             } finally {                 if (!ranAction)                     breakBarrier();             }         }          // loop until tripped, broken, interrupted, or timed out         for (;;) {             try {                 if (!timed)                     trip.await();                 else if (nanos > 0L)                     nanos = trip.awaitNanos(nanos);             } catch (InterruptedException ie) {                 if (g == generation && ! g.broken) {                     breakBarrier();                     throw ie;                 } else {                     // We're about to finish waiting even if we had not                     // been interrupted, so this interrupt is deemed to                     // "belong" to subsequent execution.                     Thread.currentThread().interrupt();                 }             }              if (g.broken)                 throw new BrokenBarrierException();              if (g != generation)                 return index;              if (timed && nanos <= 0L) {                 breakBarrier();                 throw new TimeoutException();             }         }     } finally {         lock.unlock();     } } 

在dowait(boolean timed, long nanos),可以通过时间参数来设置阻塞的时间,默认为false,在这个方法内部,每次线程调用await后,都会进行--count操作,直到index为0时,会去执行command,然后唤醒线程继续向下执行,CyclicBarrier 的计数器可以通过reset()方法重置,所以它能处理循环使用的场景。

CyclicBarrier的使用

大致的了解了CyclicBarrier的原理之后,我们写个小demo测试一下它如何使用

【代码示例】

public class Test {     public static void main(String[] args) {         int numberOfThreads = 3; // 线程数量         CyclicBarrier barrier = new CyclicBarrier(numberOfThreads, () -> {             // 当所有线程都到达障碍点时执行的操作             System.out.println("所有线程都已到达屏障,进入下一阶段");         });          for (int i = 0; i < numberOfThreads; i++) {             new Thread(new Task(barrier), "Thread " + (i + 1)).start();         }     }      static class Task implements Runnable {         private final CyclicBarrier barrier;          public Task(CyclicBarrier barrier) {             this.barrier = barrier;         }          @Override         public void run() {             try {                 System.out.println(Thread.currentThread().getName() + " 正在屏障处等待");                 barrier.await(); // 等待所有线程到达障碍点                 System.out.println(Thread.currentThread().getName() + " 已越过屏障.");             } catch (Exception e) {                 e.printStackTrace();             }         }     } } 

输出:

Thread 2 正在屏障处等待 Thread 1 正在屏障处等待 Thread 3 正在屏障处等待 所有线程都已到达屏障,进入下一阶段 Thread 3 已越过屏障. Thread 1 已越过屏障. Thread 2 已越过屏障. 

结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!

如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!
面试官:说一说CyclicBarrier的妙用!我:这个没用过

发表评论

相关文章