• 面试官:说一说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成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!
    image

  • 相关阅读:
    计算机毕业设计Java区间散件捎带平台(源代码+数据库+系统+lw文档)
    MySQL基础进阶:汇总数据
    下一个十年,数字化的发展趋势
    利用jenkins直接构件docker镜像并发布
    HashMap初始化大小
    透视投影时相机的参数设置
    ISP图像信号处理——平场校正介绍以及C++实现
    8.strtok函数
    到底什么是JS原型
    Golang 笔试面试学习第二天之range
  • 原文地址:https://www.cnblogs.com/JavaBuild/p/18137327