可循环使用的屏障CyclicBarrier(源码分析)

    前文有分析了发令枪的原理,这里先总结一下CountDownLatch和CyclicBarrier的区别。

    1.发令枪是一次性的,无法重置,循环屏障可重复使用(reset)

    2.发令枪是在所有任务都执行结束统一退出的时候使用,循环屏障是还没开始任务前统一步调的时候使用。

举个例子,在计算银行的交易总金额是我们需要使用多个线程去计算每个时间段的总金额,如果我们想要得知该银行的一天的总金额,那就得使用发令枪保证所有时间段内的总金额被计算完毕后执行总金额的计算。如果我们希望计算的线程在同一时刻并行计算的话,就得使用屏障。下面有一个简单例子,我没有使用循环的功能:

  ExecutorService executor= Executors.newFixedThreadPool(5);
        final CyclicBarrier cyclicBarrier=new CyclicBarrier(5,new Runnable(){
            public void run() {
                System.out.println("所有运动员准备就绪");

            }
        });
        for (int i = 1; i < 6; i++) {
            executor.execute(new Runner(i,cyclicBarrier));
        }
        cyclicBarrier.reset();

输出结果是:

1号运动员准备就绪
当前时间:2018-05-04 14:11:15
2号运动员准备就绪
当前时间:2018-05-04 14:11:16
3号运动员准备就绪
当前时间:2018-05-04 14:11:17
4号运动员准备就绪
当前时间:2018-05-04 14:11:18
5号运动员准备就绪
当前时间:2018-05-04 14:11:19
所有运动员准备就绪
5号运动员起跑时间:2018-05-04 14:11:19
5号运动员跑起来了
4号运动员起跑时间:2018-05-04 14:11:19
4号运动员跑起来了
3号运动员起跑时间:2018-05-04 14:11:19
3号运动员跑起来了
1号运动员起跑时间:2018-05-04 14:11:19
1号运动员跑起来了
2号运动员起跑时间:2018-05-04 14:11:19
2号运动员跑起来了

Runner的代码如下:

 static class Runner extends  Thread{
        int Number;
        CyclicBarrier cyclicBarrier;
        public Runner(int Number,CyclicBarrier cyclicBarrier){
            this.Number = Number;
            this.cyclicBarrier = cyclicBarrier;
        }
        public void run() {
            try {
                Thread.sleep(Number*1000); //为了使结果更清晰
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Number+"号运动员准备就绪");
            System.out.println("当前时间:"+sdf.format(new Date()));
            try {
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            doRun(Number);
        }
        private void doRun(int number) {
            System.out.println(Number+"号运动员起跑时间:"+sdf.format(new Date()));
            System.out.println(Number+"号运动员跑起来了");
        }
    }

从代码里面可以清楚的观察到,前4个运动员都停在了 cyclicBarrier.await();而一旦有5个运动员都准备就绪之后就可以开始跑步竞赛了。因此掌握了它的构造和使await返回的原因就能基本了解它的实现原理。

初始化构造的时候我们传入了一个线程对象,parties为5.CyclicBarrier内聚了一个Generation和持有了一个RentrantLock以及一个Condition,那么它的实现原理猜都能猜到,就是通过判断条件将未满足条件时的线程加入到Condition的等待队列中去,然后等到条件满足后使用signalAll把所有等待队列里的线程唤醒。

 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();

先不管Generation时用来干嘛的,我们到await方法去一探究竟:

return dowait(false, 0L);  

看来实际实行的dowait方法:

private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        lock.lock();//加锁
        try {
            final Generation g = generation;  //有一个**对象,先忽略

            if (g.broken)
                throw new BrokenBarrierException();

            if (Thread.interrupted()) {
                breakBarrier();//响应中断
                throw new InterruptedException();
            }

            int index = --count;   //count在构造的时候被传入的parties赋值
            if (index == 0) {  // 满足条件
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();//这里就是实现我们传入的线程在满足条件后第一个执行的条件
                    ranAction = true;
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)  //这里若是仍然true,意味着上面的代码未执行成功
                        breakBarrier();
                }
            }

            
            for (;;) {//不满足条件
                try {
                    if (!timed)   
                        trip.await();//因为await无参数,所以我们的程序会执行到这-----core
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);//nanos=0,如果不为0的时候线程会等待一段时间都自行返回
                } 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();

                if (g != generation)
                    return index;

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

看来猜想完全正确,接下来贴出nextGeneration的代码:

  private void nextGeneration() {

        trip.signalAll();  //core

        count = parties;
        generation = new Generation();  //可循环就是说的这个,每次唤醒完成之后重置状态
    }

 breakBarrier()时用来响应中断的,若是某处await执行的时候碰到了外部中断,这时候会在该方法唤醒所有在等待队列的线程。最后讲一下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();
        }
    }

实际上是理解成人工地未释放当前状态,然后重置回之前地状态。

从另一方面看发令枪和可循环屏障都可以实现同一个功能,比如说统一步调一起走,对于发令枪来说可以在线程任务开始时使用await阻塞,然后在主线程countdown释放所有线程。对于可循环屏障来说只要await放好就ok了。

猜你喜欢

转载自blog.csdn.net/qq_36243399/article/details/80194262
今日推荐