CyclicBarrier源码分析

248 阅读4分钟

概述

CyclicBarrier循环屏障,可以实现让一组线程相互等待,一组线程到达该等待点后同时继续往下执行,CyclicBarrier计数清零,然后进行下一次计数。
可以理解为奥运赛道,因为只有8个赛道,裁判员一次只让8名选手赛跑,8名选手全部就绪,裁判员发号施令,选手们同时起跑,然后让下一组选手入场。

对比CountDownLatch

使用例子

这里只展示一个例子,更多例子可以进入github

public class CyclicBarrierTest {
    private final static CyclicBarrier cyclicBarrier = new CyclicBarrier(3);

    static class Worker implements Runnable {
        @Override
        public void run() {
            try {
                System.out.println(currentThread().getName() + ": start");
                cyclicBarrier.await();
                System.out.println(currentThread().getName() + ": end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final int N = 30;
        for (int i = 0; i < N; ++i) {
            TimeUnit.SECONDS.sleep(1);
            new Thread(new Worker()).start();
        }
    }
}

运行结果:

Thread-0: start
Thread-1: start
Thread-2: start
Thread-2: end
Thread-0: end
Thread-1: end
Thread-3: start
Thread-4: start
Thread-5: start

可以看到上面的结果是每3个释放,原因就是使用了CyclicBarrier,下面我们一起看看源码内部的实现。\

源码结构

public class CyclicBarrier {
    //用于循环使用,以及标记本次屏障是否被毁坏
    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();

    //进行递减计数,初始化值等于parties,递减到0后,释放一组线程,再次赋值为parties
    private int count;
    。。。
}

可以看到源码使用了ReentrantLock的条件等待Condition,这两个源码在前面文章里已经分析了,就不做详细介绍了。
await方法

public int await() throws InterruptedException, BrokenBarrierException {
    try {
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}
private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
           TimeoutException {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        final Generation g = generation;
        //如果当代屏障标记已被破坏,抛出BrokenBarrierException异常
        if (g.broken)
            throw new BrokenBarrierException();

        //如果线程被中断
        //将本组的屏障标记设为已被破坏并唤醒其他线程
        if (Thread.interrupted()) {
            breakBarrier();
            throw new InterruptedException();
        }

        //将计数递减
        int index = --count;
        if (index == 0) {  // tripped
            boolean ranAction = false;
            try {
                //减至0则执行前置命令
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();
                ranAction = true;
                //唤醒本组线程,重置count值为parties
                nextGeneration();
                return 0;
            } finally {
                if (!ranAction)
                    breakBarrier();
            }
        }

        // 运行到这里说明count值不为0,说明本组屏障没有全部就位,进行等待操作
        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 {
                    Thread.currentThread().interrupt();
                }
            }
            //如果broken为true说明当代屏障已被破坏,抛出BrokenBarrierException异常
            if (g.broken)
                throw new BrokenBarrierException();
            //如果当代屏障已被重置,则返回
            if (g != generation)
                return index;
            //如果本次唤醒等待时间超时唤醒
            //将当代屏障设为已被破坏,进行本组唤醒的唤醒,抛出超时异常
            if (timed && nanos <= 0L) {
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
        //解锁
        lock.unlock();
    }
}
  1. 将generation赋值给g,暂存当代屏障(因为CyclicBarrier是重复利用的,当计数值减为0后,会重置count值,开启新的一轮屏障,每一轮叫当代屏障)
  2. count值最开始等于CyclicBarrier的parties的属性,即将目标计数递减,减为0,后判断是否有前置命令(CyclicBarrier构造函数中支持,当达到计数时,先进行一个线程执行方法,即前置命令),再然后调用nextGeneration,唤醒本组线程,重置count值为parties
  3. 如果count值未减为0,说明本组屏障未全部到达,需要进行等待(一种是超时等待,一种是无限制等待)
  4. 如果等待过程中发生了中断异常,将当代屏障设为已被破坏,进行本组唤醒的唤醒
  5. 线程被唤醒后,会判断当代屏障标记是否被置为已破坏
  6. 如果线程是被等待超时唤醒,设置当代屏障被破坏,抛出超时异常(这里可以看到如果是超时唤醒的CyclicBarrier是不会重置开启新的一轮屏障的)

我们进一步看一下方法调用最多的breakBarrier方法和nextGeneration方法。 breakBarrier方法

private void breakBarrier() {
    //将当代屏障设为已被破坏
    generation.broken = true;
    //count重置为parties
    count = parties;
    //唤醒本组屏障中的其他等待线程
    trip.signalAll();
}

nextGeneration方法

private void nextGeneration() {
    // 唤醒所有本轮的屏障线程
    trip.signalAll();
    //count重置为parties
    count = parties;
    //设置下一次屏障标志
    generation = new Generation();
}

再来看看CyclicBarrier reset方法
reset方法

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

可以看到reset方法就是将当代屏障设为已被破坏,再开启新的一轮屏障(需要注意的是本组的屏障线程后续代码是不会被执行的)
CyclicBarrier就分析到这里了。