深圳幻海软件技术有限公司 欢迎您!

面试官:CyclicBarrier有了解过吗?

2023-02-28

前言Java提供了一些非常好用的并发工具类,不需要我们重复造轮子,本节我们讲解CyclicBarrier,一起来看下吧~CyclicBarrier这个跟我们上节讲的CountDownLatch有点类似,从字面意思讲是相当于一个可循环的屏障,他与CountDownLatch不同的是它可以重复利用,下一

前言

Java提供了一些非常好用的并发工具类,不需要我们重复造轮子,本节我们讲解CyclicBarrier,一起来看下吧~

CyclicBarrier

这个跟我们上节讲的CountDownLatch有点类似,从字面意思讲是相当于一个可循环的屏障,他与CountDownLatch不同的是它可以重复利用,下一步的操作以,依赖上一步是否完成,就像去银行办业务一样,排在你前面的人办好了才轮到你,我们继续通过上节的例子,来改写一下它,这里我偷个懒,实际业务中尽量用类编写,不要直接new Thread。

public class CyclicBarrierTest {    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {        CyclicBarrier cyclicBarrier = new CyclicBarrier(1);        IntStream.range(0, 10).forEach(i -> {            new Thread(() -> {                try {                    Thread.sleep(2000);                    System.out.println("worker 1------> " + i);                    cyclicBarrier.await();                    Thread.sleep(2000);                    System.out.println("worker 2------> " + i);                    cyclicBarrier.await();                    Thread.sleep(2000);                    System.out.println("worker 3------> " + i);                    cyclicBarrier.await();                } catch (InterruptedException | BrokenBarrierException e) {                    e.printStackTrace();                }            }).start();        });        System.out.println("completed !");    }}
  • 1.

实际输出:

completed !worker 1------> 9worker 1------> 0worker 1------> 6worker 1------> 7worker 1------> 5worker 1------> 4worker 1------> 1worker 1------> 3worker 1------> 2worker 1------> 8worker 2------> 7worker 2------> 6worker 2------> 5worker 2------> 2worker 2------> 3worker 2------> 1worker 2------> 8worker 2------> 0worker 2------> 9worker 2------> 4worker 3------> 6worker 3------> 3worker 3------> 2worker 3------> 5worker 3------> 7worker 3------> 8worker 3------> 1worker 3------> 0worker 3------> 9worker 3------> 4
  • 1.

可以看到在即使在多线程下,每个操作都需要上一个await任务之后执行,使用很简单,也很好理解。

知其然知其所以然 & 源码剖析

下面我们就一起探究一下,它是如何做到的?

同样的,我们先看构造函数。

public CyclicBarrier(int parties) {    this(parties, null);}public CyclicBarrier(int parties, Runnable barrierAction) {    if (parties <= 0) throw new IllegalArgumentException();    this.parties = parties;    this.count = parties;    this.barrierCommand = barrierAction;}
  • 1.

​默认barrierAction是null, 这个参数是Runnable参数,当最后线程达到的时候执行的任务,刚刚的例子中没有演示,大家可以在初始化的时候传入一个,打印一下当前的线程名称,这样理解起来比较容易点,parties int型,它的意思是参与的线程数。

我们再看它的定义, 可以看到它没有继承任何类或实现任何接口​。

public class CyclicBarrier { .... }
  • 1.

await

我们重点看下这个方法。

public int await() throws InterruptedException, BrokenBarrierException {    try {        return dowait(false, 0L);    } catch (TimeoutException toe) {        throw new Error(toe); // cannot happen    }}
  • 1.

这个方法干嘛用的呢?等到所有各方都在此屏障上调用了await 。如果当前线程不是最后到达的,则出于线程调度目的将其禁用并处于休眠状态,除了以下情况:

  • 最后一个线程到达;或者。
  • 其他一些线程中断当前线程;或者。
  • 其他一些线程中断了其他等待线程之一;或者。
  • 其他一些线程在等待屏障时超时;或者。
  • 其他一些线程在此屏障上调用reset 。

再看dowait(), 它是一个私有方法。

private int dowait(boolean timed, long nanos)        throws InterruptedException, BrokenBarrierException,               TimeoutException {        // 全局锁                final ReentrantLock lock = this.lock;        lock.lock();        try {            // 每次使用屏障都会生成一个实例            // private Generation generation = new Generation();            final Generation g = generation;            // broken字面意思破坏,如果被破坏了就抛异常            if (g.broken)                throw new BrokenBarrierException();            // 线程中断检测            if (Thread.interrupted()) {                breakBarrier();                throw new InterruptedException();            }            // 剩余的等待线程数            int index = --count;            // 最后线程到达时             if (index == 0) {  // tripped                // 标记任务是否被执行(就是传进入的runable参数)                boolean ranAction = false;                try {                    final Runnable command = barrierCommand;                    // 执行任务                    if (command != null)                        command.run();                    ranAction = true;                    // 完成后 进行下一组 初始化 generation 初始化 count 并唤醒所有等待的线程                     //                     // private void nextGeneration() {                    //     // signal completion of last generation                    //     trip.signalAll();                    //     // set up next generation                    //     count = parties;                    //     generation = new Generation();                    // }                    nextGeneration();                    return 0;                } finally {                    if (!ranAction)                        breakBarrier();                }            }            // index 不为0时 进入自旋            for (;;) {                try {                    // 先判断超时 没超时就继续等着                    if (!timed)                        trip.await();                        // 如果超出指定时间 调用 awaitNanos 超时了释放锁                    else if (nanos > 0L)                        nanos = trip.awaitNanos(nanos);                        // 中断异常捕获                } catch (InterruptedException ie) {                    // 判断是否被破坏                    if (g == generation && ! g.broken) {                        //  private void breakBarrier() {                        //     generation.broken = true;                        //     count = parties;                        //     trip.signalAll();                        // }                        breakBarrier();                        throw ie;                    } else {                        // 否则的话中断当前线程                        Thread.currentThread().interrupt();                    }                }                // 被破坏抛异常                if (g.broken)                    throw new BrokenBarrierException();                // 正常调用 就返回                 if (g != generation)                    return index;                // 超时了而被唤醒的情况 调用 breakBarrier()                if (timed && nanos <= 0L) {                    breakBarrier();                    throw new TimeoutException();                }            }        } finally {            // 释放锁            lock.unlock();        }    }
  • 1.

如果被破坏了怎么恢复呢?来看下reset, 源码很简单,break之后重新生成新的实例,对应的会重新初始化count,在dowait里index==0也调用了nextGeneration,所以说它是可以循环利用的。

public void reset() {    final ReentrantLock lock = this.lock;    lock.lock();    try {        breakBarrier();   // break the current generation        nextGeneration(); // start a new generation    } finally {        lock.unlock();    }}
  • 1.

结束语

cyclicBarrier源码相对简单一些,下节给大家讲下Phaser,它是增强版的CountDownLatch,它的实现相对更加复杂一点 。