CyclicBarrier的工作原理

简单介绍

CyclicBarrier是juc下的多线程工具类,翻译过来就是循环栅栏的意思,在使用CyclicBarrier的时候需要指定线程数

CyclicBarrier cyclicBarrier = new CyclicBarrier(3);

在多线程协作的过程中,只有当指定个数的线程都到达了预定位置线程才会往下执行。下面从源码来分析一下它的工作原理

工具初始化

当调用如下代码初始化CyclicBarrier工具类的时候,实际上是指定了需要协作的线程数

CyclicBarrier cyclicBarrier = new CyclicBarrier(3);

CyclicBarrier的构造方法仅仅是记录我们传入的3

public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties; //指定的线程数,
        this.count = parties; //初始化为指定的线程数
        //指定的线程数被记录的两次是因为 parties是final的,因为是循环栅栏,
        //所以需要在栅栏打开后再次将count赋值为parties,实际多线程使用过程中我们
        //只需要关注count便可
        this.barrierCommand = barrierAction; //打开栅栏后需要执行的runnable
    }

CyclicBarrier类的成员变量如下

/** The lock for guarding barrier entry */
    private final ReentrantLock lock = new ReentrantLock();
    /** Condition to wait on until tripped */
    private final Condition trip = lock.newCondition();
    /** The number of parties */
    private final int parties;
    /* The command to run when tripped */
    private final Runnable barrierCommand;
    /** The current generation */
    private Generation generation = new Generation();

到目前为止,不用过多关注以上的代码,只需要记住在我们初始化CyclicBarrier的时候指定了一个线程数

测试类

这是用来测试CyclicBarrier工作原理的代码

public class TestCyclicBarrier {

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
        System.out.println("");
        new Thread(() -> {
            System.out.println("线程1开始等待指挥员到来");
            try {
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println("线程1等待结束,开始执行任务");
        }).start();
        new Thread(() -> {
            System.out.println("线程2开始等待指挥员到来");
            try {
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println("线程2等待结束,开始执行任务");
        }).start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("指挥员就位");
        try {
            cyclicBarrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
        System.out.println("任务开始");
    }
}

执行结果:

线程1开始等待指挥员到来
线程2开始等待指挥员到来
指挥员就位
任务开始
线程1等待结束,开始执行任务
线程2等待结束,开始执行任务

从上面我们能看到,整个测试程序只调用了CyclicBarrier的一个方法:await(),所以只需要从await方法入手

await方法

await()方法直接调用内部的dowait方法

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

原因是CyclicBarrier提供的await方法重载了两种方法,一种是带超时时间的,一种是不带超时时间的,以下是带超时时间的方法

public int await(long timeout, TimeUnit unit)
        throws InterruptedException,
               BrokenBarrierException,
               TimeoutException {
        return dowait(true, unit.toNanos(timeout));
    }

所以我们需要研究dowait()方法

dowait方法

private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        //首先加锁
        lock.lock();
        try {
        //记录当前CyclicBarrier的generation成员变量,以待后用
            final Generation g = generation;

            if (g.broken)
                throw new BrokenBarrierException();
		//前面两步不用看,看源码需要忽略部分细节,而理清整体脉络,
		//第一次进入dowait的线程肯定不会抛出异常,所以没必要看
            if (Thread.interrupted()) {
            	//判断如果当前线程在等待中被中断便跳出栅栏,这个也不是主线逻辑,忽略
                breakBarrier();
                throw new InterruptedException();
            }
		//从这里开始,是正常线程走的逻辑
            //首先将count-1,记录剩余的数量,count就是我们初始化指定的线程数
            int index = --count;
            //如果index==0,这意味着最后一个线程执行到这里才会index==0,记住这里,先跳到后面
            if (index == 0) {  
            	//最后一个线程会直接走到这里
            	//首先标记是否需要执行runnable的方法(初始化的时候可以指定)
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
              	/**以上执行了runnable的run方法
              	* 并且是最后一个线程直接执行的,并没有开启新的线程异步执行哦
              	* 执行nextGeneration()后直接return 0了,所以nextGeneration()方法
              	* 里是唤醒其他等待线程,并且重置循环栅栏,该方法代码贴在后面
              	* 至此整个循环栅栏工作已经完成
               **/
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }

            // for循环,意味着除了最后一个线程,都将在这循环等待
            for (;;) {
                try {
                //trip.await trip是lock.condition(),这里就是直接等待并且释放锁
                //这里的if else只是判断是直接等待还是等待一段时间
                    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();
		/**这里是关键,因为这里有return,什么时候return?
		* 就是g != generation的时候,前面方法开始的时候每个线程会记录这个线程刚进入
		* dowait方法的时候的generation变量,而所有等待的线程都不会改这个变量,只有
		* 最后一个线程会改这个变量,只有在最后一个线程处理完,并且改了generation以后
		* 其他在等待的线程才能正常退出,所以让我们回到前面if (index == 0)方法
		**/
                if (g != generation)
                    return index;

                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }
private void nextGeneration() {
        // signal completion of last generation
        trip.signalAll();
        // set up next generation
        count = parties;
        generation = new Generation();
    }
private void breakBarrier() {
        generation.broken = true;
        count = parties;
        trip.signalAll();
    }

总结

CyclicBarrier的await()方法的流程已经分析完毕了,最后用文字来总结一下它的工作流程

  1. 首先初始化CyclicBarrier指定需要拦截的线程数,指定的线程数记做count
  2. 然后每个线程执行await方法的时候先加锁然后将count -1,如果count-1≠0那么就需要执行等待
  3. 需要等待的线程执行lock.condition().await()方法循环等待并且释放锁
  4. 最后一个线程到达的时候count-1=0,便会执行指定的runnable的run方法
  5. 执行runnable的run方法后重置count和genaration,然后唤醒其他等待的线程,当前线程退出
  6. 等待线程被唤醒,判断generation被更改,退出循环等待
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值