关于java多线程浅析六: CyclicBarrier的原理分析和使用

什么是CyclicBarrier

上一篇文章中介绍完了Condition条件,这篇文章就介绍一下使用到了Condition了的CyclicBarrier同步辅助类。CyclicBarrier翻译过来可以理解成“关卡”、“屏障”。想象这样一个场景:100米赛跑比赛上,有4名运动员参见比赛,比较公平的赛法是——等待大家都做好起跑动作,准备好时,裁判员再发信号枪,然后大家一起跑。而在多线程的场景下,每一个运动员都一个线程,而裁判员就是这个CyclicBarrier。

当然这里要注意要将CyclicBarrier与之后要介绍的CountDownLatch区分开来,这两个还是很好区分的,这个待介绍CountDownLatch的时候进行区分。

废话不多说,我们先用一个小demo看看CyclicBarrier到底做什么用的

CyclicBarrier的使用示例

为了理解方便,那我就很简单的用打印的方式模拟一下上面介绍的时候说到的场景:

/**
 * Created by fei on 2017/6/7.
 */
public class CyclicBarrierDemo {

    public static final int INIT_SIZE = 4;
    private static CyclicBarrier barrier;

    public static void main(String[] args) {
        System.out.println("开启CyclicBarrier屏障(裁判员就位)");
        //初始化CyclicBarrier
        barrier = new CyclicBarrier(INIT_SIZE, new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName()+"线程通知:所有线程都已经准备好了,CyclicBarrier屏障去除(所有运动员都准备完毕,发信号枪)");
            }
        });
        //开启4个线程,充当运动员
        for (int i=0;i<INIT_SIZE;i++){
            new ThreadDemo().start();
        }

    }

    static class ThreadDemo extends Thread {
        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName()+"线程准备好了,等待CyclicBarrier屏障去除(一名运动员准备好了)");

                barrier.await();

                System.out.println(Thread.currentThread().getName()+"线程继续运行(开始跑)");

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

某次运行结果:

开启CyclicBarrier屏障(裁判员就位)
Thread-0线程准备好了,等待CyclicBarrier屏障去除(一名运动员准备好了)
Thread-1线程准备好了,等待CyclicBarrier屏障去除(一名运动员准备好了)
Thread-3线程准备好了,等待CyclicBarrier屏障去除(一名运动员准备好了)
Thread-2线程准备好了,等待CyclicBarrier屏障去除(一名运动员准备好了)
Thread-2线程通知:所有线程都已经准备好了,CyclicBarrier屏障去除(所有运动员都准备完毕,发信号枪)
Thread-2线程继续运行(开始跑)
Thread-1线程继续运行(开始跑)
Thread-0线程继续运行(开始跑)
Thread-3线程继续运行(开始跑)

根据上面这个简单的小示例,应该不难看出CyclicBarrier的意思。再上一张网上盗的图加强理解:

这里写图片描述

而CyclicBarrier到底是怎么实现的呢?接下来我们就简单分析一下CyclicBarrier的源码。

CyclicBarrier源码解析

先看一下CyclicBarrier中成员变量的组成:

这里写图片描述

可以看出,CyclicBarrier是由ReentrantLock和Condition来实现的。具体每个变量都有什么意义,我们在分析源码的时候具体说。
我们主要从CyclicBarrier的构造方法和它的await方法分析说起。

CyclicBarrier构造函数

CyclicBarrier有两个构造函数:

//带Runnable参数的函数
 public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;//有几个运动员要参赛
        this.count = parties;//目前还需要几个运动员准备好
        //你要在所有线程都继续执行下去之前要执行什么操作,可以为空
        this.barrierCommand = barrierAction;
    }
//不带Runnable参数的函数
 public CyclicBarrier(int parties) {
     this(parties, null);
 }

其中,第二个构造函数调用的是第一个构造函数,这个 Runnable barrierAction 参数是什么呢?其实在上面的小示例中我们就用到了这个Runnable参数,它就是在所有线程都准备好之后,满足Barrier条件时,并且在所有线程继续执行之前,我们可以执行这个Runnable。但是值得注意的是,这不是新起了一个线程,而是通过最后一个准备好的(也就是最后一个到达Barrier的)线程承担启动的。这一点我们在上面示例中打印的运行结果中也可以看出来:Thread-2线程是最后一个准备好的,就是它执行的这个barrierAction。
这里parties和count不要混淆,parties是表示必须有几个线程要到达Barrier,而count是表示目前还有几个线程未到达Barrier。也就是说,只有当count参数为0时,Barrier条件即满足,所有线程可以继续执行。
count变量是怎么减少到0的呢?是通过Barrier执行的await方法。下面我们就看一下await方法。

await方法

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

await方法调用的dowait方法:

    /**
     * Main barrier code, covering the various policies.
     */
    private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        lock.lock(); //获取ReentrantLock互斥锁
        try {
            final Generation g = generation;//获取generation对象

            if (g.broken)//如果generation损坏,抛出异常
                throw new BrokenBarrierException();

            if (Thread.interrupted()) {
               //如果当前线程被中断,则调用breakBarrier方法,停止CyclicBarrier,并唤醒所有线程
                breakBarrier();
                throw new InterruptedException();
            }

            int index = --count; // 看到这里了吧,count减1 
            //index=0,也就是说,有0个线程未满足CyclicBarrier条件,也就是条件满足,
            //可以唤醒所有的线程了
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                   //这就是构造器的第二个参数,如果不为空的话,就执行这个Runnable的run方法,
                   //你看,这里是执行的是run方法,也就是说,并没有新起一个另外的线程,
                   //而是最后一个执行await操作的线程执行的这个run方法。
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    //这个nextGeneration下面会说,就是唤醒了所有的等待线程,
                    //并且更新了generation
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)//保证一定能唤醒所有线程
                        breakBarrier();
                }
            }

            // loop until tripped, broken, interrupted, or timed out
            //上面是源代码中的注释,说的很清楚了,
            //当barrier的条件满足、当前线程被中断或者已经超时才会跳出循环
            for (;;) {
                try {
                    if (!timed)//如果没有指定超时参数,则直接调用Condition的await方法。
                        trip.await();
                    else if (nanos > 0L)//否则根据超时时间调用awaitNanos方法
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    //执行过程中,线程被中断的话,看一下generation是否有该改变,并且是否已经破坏
                    if (g == generation && ! g.broken) {
                        breakBarrier();//执行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)//如果当前generation已经损坏,抛出异常
                    throw new BrokenBarrierException();

                if (g != generation)//如果generation已经更新换代,则返回index
                    return index;
                //如果是参数是超时等待,并且已经超时,则执行breakBarrier()方法
                //唤醒所有等待线程。
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }

简单来说,如果不发生异常,线程不被中断,那么dowait方法会调用Condition的await方法(具体Condition的原理请看前面的文章),直到所有线程都准备好,即都执行了dowait方法,(做count的减操作,直到count=0),即CyclicBarrier条件已满足,就会执行唤醒线程操作,也就是上面的nextGeneration()方法。可能大家会有疑惑,这个Generation是什么东西呢?其实这个Generation定义的很简单,就一个布尔值的成员变量:

private Generation generation = new Generation();

private static class Generation {
    boolean broken = false;
}

Generation 可以理解成“代”,我们要知道,CyclicBarrier是可以重复使用的,CyclicBarrier中的同一批线程属于同一“代”,当所有线程都满足了CyclicBarrier条件,执行唤醒操作nextGeneration()方法时,会新new 出一个Generation,代表一下“代”。

nextGeneration的源码

    private void nextGeneration() {
        // signal completion of last generation
        trip.signalAll();//调用Condition的signalAll方法,唤醒所有await的线程
        // set up next generation
        count = parties;//重置count值
        //生成新的Generation,表示上一代的所有线程已经唤醒,进行更新换代
        generation = new Generation(); 
    }

breakBarrier源码

再来看一下breakBarrier的代码,breakBarrier方法是在当前线程被中断时执行的,用来唤醒所有的等待线程:

    private void breakBarrier() {
        generation.broken = true;//表示当代因为线程被中断,已经发成损坏了
        count = parties;//重置count值
        trip.signalAll();//调用Condition的signalAll方法,唤醒所有await的线程
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值