CyclicBarrier源码解析

288 阅读5分钟

         
小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

图片

《教父》

Never hate your enemies. If affects your judgment.

这几天把《教父》第一、二、三部电影都看了一遍,以前对这部电影早有耳闻,说是经典中的经典,男人必看的电影,我去,这几天沉下心全部看完之后,真的不愧是经典,无论是第一部中马龙白兰度饰演的第一代教父维托·唐·柯里昂,还是第二、三部阿尔·帕西诺饰演的迈克·柯里昂,都把教父这一形象表演的淋漓尽致,真的是不可多得的好电影,电影很长,但很值得一看,强烈推荐大家看一看哦。好了说完电影,进入今天的主题,一起来手撕CyclicBarrier。

CyclicBarrier,回环栅栏,它会阻塞一组线程直到这些线程同时达到某个条件才继续执行。它与CountDownLatch很类似,但又不同,CountDownLatch需要调用countDown()方法触发条件,而CyclicBarrier不需要,它就像一个栅栏一样,当一组线程都到达了栅栏处才继续往下走,而且是可重复利用的。

先来看一个简单的使用示例。

/**
 * @ClassName CyclicBarrierDemo
 * @Description: TODO
 * @Author lirui
 * @Date 2020/4/26
 * @Version V1.0
 **/
public class CyclicBarrierDemo {

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 1; i <= 3; i++) {
            executorService.execute(new Runnable() {

                @Override
                public void run() {

                    System.out.println("都进来吧。等一等哦");
                    try {
                        cyclicBarrier.await();
                        Thread.sleep(3000);
                        System.out.println("一起释放青春把");


                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}


// result

都进来吧。等一等哦
都进来吧。等一等哦
都进来吧。等一等哦
一起释放青春把
一起释放青春把
一起释放青春把

使用一个CyclicBarrier使得三个线程保持同步,当三个线程同时到达cyclicBarrier.await();处大家再一起往下运行。

>>>>

内部构造及属性

    private static class Generation {
        // 表示当前“代”是否被打破,如果代被打破,那么再来到这一代的线程就会直接抛出异常
        // 且在这一代被挂起的线程都会被唤醒,然后抛出异常。
        boolean broken = false;
    }

    /**
     * 重入锁
     */
    private final ReentrantLock lock = new ReentrantLock();
    /**
     * 条件锁,线程来了先绊倒你,达到一定数量再唤醒
     */
    private final Condition trip = lock.newCondition();
    /**
     * 需要等待的线程数量
     */
    private final int parties;
    /* 当唤醒的时候执行的命令 */
    private final Runnable barrierCommand;
    /**
     * 当前代
     */
    private Generation generation = new Generation();

    /**
     * 当前这一代还有多少线程还没到位
     */
    private int count;

CyclicBarrier内部维护了一个静态内部类Generation 。

Generation,中文意思是一代人的代,用于控制CyclicBarrier的循环使用。

比如,上面示例中的三个线程完成后进入下一代,继续等待三个线程达到栅栏处再一起执行,而CountDownLatch则做不到这一点,CountDownLatch是一次性的,无法重置其次数。

通过属性可以看到,CyclicBarrier内部是通过重入锁的条件锁来实现的,那么我们通过这个属性可以设想一下,假如初始时count = parties = 3,当第一个线程到达栅栏处,count减1,然后把它加入到Condition的队列中,第二个线程到达栅栏处也是如此,第三个线程到达栅栏处,count减为0,调用Condition的signalAll()通知另外两个线程,然后把它们加入到AQS的队列中,等待当前线程运行完毕,调用lock.unlock()的时候依次从AQS的队列中唤醒一个线程继续运行,也就是说实际上三个线程先依次(排队)到达栅栏处,再依次往下运行,这只是个假设,下面就来通过源码来看下是不是这样来实现的。

>>>>

await方法

  public int await() throws InterruptedException, BrokenBarrierException {
        try {
            // 调用dowait方法,不需要指定超时
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }

    /**
     * timed:false:不需要指定超时,true:需要指定超时
     * nanos:当前需要等待的线程数
     */
    private int dowait(boolean timed, long nanos)
            throws InterruptedException, BrokenBarrierException,
            TimeoutException {

        final ReentrantLock lock = this.lock;
        // 加锁
        // 加锁是因为barrier的挂起和唤醒的组件是condition,
        // condition需要依赖lock锁
        lock.lock();
        try {
            // 当前代
            final Generation g = generation;
            // 如果当前代被打破,调用await方法的线程会抛出异常
            if (g.broken)
                throw new BrokenBarrierException();
            // 中断检查
            if (Thread.interrupted()) {
                // count重置
                // 唤醒trip 条件队列的所有线程
                breakBarrier();
                throw new InterruptedException();
            }
            // count值减1
            int index = --count;
            // 如果数量减到0,走这个if逻辑(最后一个线程走到这里)
            if (index == 0) {

                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    // 不为空就去执行
                    if (command != null)
                        command.run();
                    ranAction = true;
                    // 调用下一代方法
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }
            // 自旋,这个循环只有非最后一个线程可以走到
            for (; ; ) {
                try {
                    //条件成立说明当前线程不指定超时时间
                    if (!timed)
                        // 释放锁
                        // 调用condition的await方法进入条件队列尾部
                        // 等待被唤醒
                        trip.await();
                    else if (nanos > 0L)
                        //说明当前线程调用await方法是指定了超时时间的
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    // 何时会抛出中断异常?
                    //Node节点在条件队列内时收到中断信号时会抛出中断异常

                    // 当前代并没有变化并检查当前代是否被打破
                    if (g == generation && !g.broken) {
                        // 重置count
                        // 唤醒条件队列所有线程
                        breakBarrier();
                        throw ie;
                    } else {
                        //进入这里的几种情况

                        // 1. 代发生变化

                        // 2. 代没有发生变化,但代已经被打破了

                        //
                        Thread.currentThread().interrupt();
                    }
                }
                //1. 当前代被打破
                // 2. 当前barrier开启了新的一代
                // 等待超时
                if (g.broken)
                    // 线程唤醒后依次抛出异常
                    throw new BrokenBarrierException();


                if (g != generation)
                    return index;

                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }

private void nextGeneration() {
    // 将等待队列中的线程全部唤醒
    trip.signalAll();
    // count值重置
    count = parties;
    // 开启新的一代
    generation = new Generation();
}

   /**
     * 打破barrier屏障,在屏障内的线程都会抛出异常
     */
    private void breakBarrier() {
        //设置weitrue,表示这一代被打破,再来到这一代的线程会抛出异常
        generation.broken = true;
        // count重置
        count = parties;
        // 唤醒队列中的所有线程
        trip.signalAll();
    }

  1. 首先就是通过ReentrantLock进行加锁,然后拿到当前代,接着判断当前代是否被打破,被打破就抛出异常,接着判断是否被中断,被终中断的话,首先调用breakBarrier方法对count数进行重置,然后唤醒所有在条件队列的线程。

  2. 当count值减到为0的时候,那么就代表所有线程都到达了栅栏处,就要开始唤醒所有线程,通过调用下一代的方法nextGeneration(),通过源码可知,这个方法主要做了三件事,唤醒等待队列的线程,将count值重置,开启新的一代。

  3. 如果count值没有减到0,那么执行await方法的线程就要被添加进条件队列,等待被唤醒。

下面就借用网上的一张运行流程图来帮助大家更好的理解。

图片

>>>>

总结

  1. CyclicBarrier会使一组线程阻塞在await()处,当最后一个线程到达时唤醒(只是从条件队列转移到AQS队列中)前面的线程大家再继续往下走;

  2. CyclicBarrier不是直接使用AQS实现的一个同步器;

  3. CyclicBarrier基于ReentrantLock及其Condition实现整个同步逻辑;

cyclicBarrier与CountDownLatch的异同?

  1. 两者都能实现阻塞一组线程等待被唤醒;

  2. 前者是最后一个线程到达时自动唤醒;

  3. 后者是通过显式地调用countDown()实现的;

  4. 前者是通过重入锁及其条件锁实现的,后者是直接基于AQS实现的;

  5. 前者具有“代”的概念,可以重复使用,后者只能使用一次;

  6. 前者只能实现多个线程到达栅栏处一起运行;

  7. 后者不仅可以实现多个线程等待一个线程条件成立,还能实现一个线程等待多个线程条件成立

如果觉得本文章对你有用,麻烦点个赞,加个关注哦