玩转高并发系列----JUC并发工具类(二)

在前一章节我们详细分析了Semaphore以及CountDownLatch的基本用法及实现原理,并且在将CountDownLatch时,也举了一个运动员赛跑的栗子。下面我将接着这个栗子详细分析CyclicBarrier的基本用法及底层实现原理。

CyclicBarrier
  1. 顾名思义,是一个可被循环使用的屏障。在前一章讲述CountDownLatch的时候说道,CountDownLatch可以用在等待指定的线程数执行完成,即到达一个指定的时间节点后,同时开始其他的任务。而CyclicBarrier在其上做了更深层次的优化—当达到一个指定的节点后,CyclicBarrier会被重置,从而可以再次被利用,等待线程到达下一个时间节点。

还是沿用上一章节中,运动员的栗子。运动员需要准备,裁判需要等待所有运动员准备好后,才能开始鸣枪开始,这里是第一个时间节点,跑完后,需要等所有运动员都到达终点后,才能开始统计各个运动员的名次,这是第二个时间节点,需要等待所有运动员的名次统计好后,才能举行颁奖,这又是第三个时间节点。这个时候,如果使用CountDownLatch实现的,我们需要同时定义三个CountDownLatch对象,因为CountDownLatch中的state变量为0后,不会被重置为初始值,所以不能被重复循环利用。此时可以用CyclicBarrier来解决这个问题。

/*
*runner-2 is Ready!
runner-3 is Ready!
runner-1 is Ready!
runner-4 is Ready!
runner-5 is Ready!
=================All runners ready============
runner-2 arrive!
runner-4 arrive!
runner-1 arrive!
runner-3 arrive!
runner-5 arrive!
=================All runners ready============
runner-2 has been recorded...
runner-1 has been recorded...
runner-4 has been recorded...
runner-5 has been recorded...
runner-3 has been recorded...
=================All runners ready============
**/
ublic class CyclicBarrierDemo {
    public static void main(String[] args) {
        String prefix = "runner-";
        // 指定需要等待5个运动员同时完成某件事后,再执行某项任务
        // 同时指定了一个lambda表达式作为第二个参数,这个参数表示所有线程到达后,执行的任务(有最后一个达到的线程执行)
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> System.out.println("=================All runners ready============"));
        // 模拟五个运动员,即五个线程
        IntStream.rangeClosed(1, 5).forEach(i -> new Runner(cyclicBarrier, prefix + i).start());

    }

    private static class Runner extends Thread {

        private String name;
        private CyclicBarrier cyclicBarrier;

        public Runner(CyclicBarrier cyclicBarrier, String name) {
            this.cyclicBarrier = cyclicBarrier;
            this.name = name;
        }

        @Override
        public void run() {
            try {
            // 随机休眠一段时间:模拟准备时间
                sleepRandom();
                System.out.println(this.name + " is Ready!");
                // 同步阻塞,等待其他运动员准备完成
                cyclicBarrier.await();

				// 开始跑步后,模拟各个运动员跑步时间
                sleepRandom();
                System.out.println(this.name + " arrive!");
                // 等待所有运动员到达现场
                cyclicBarrier.await();

				// 模拟记录运动员名次的事件
                sleepRandom();
                System.out.println(this.name + " has been recorded...");
                // 等待所有运动员,被记录,开始颁奖
                cyclicBarrier.await();
            } catch (Exception e) {
                System.out.println(this.name + " Error");
            }
        }
    }
}
  1. 源码分析:CyclicBarrier没有直接基于AQS实现同步过程,而是通过ReentrantLock+Condition的方式实现的。

首先一起来看一下其构造函数:构造函数比较简单,就是完成基本的赋值操作

    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        // 指定需要等待的线程总数
        this.parties = parties;
        // 记录仍然需要等待的线程个数:每达到一个线程处于阻塞状态时,count-1,
        // 当count为0时,唤醒所有等待的线程
        this.count = parties;
        // 指定当parties个线程达到,即count为0时,执行的回调方法。
        this.barrierCommand = barrierAction;
    }

接下来重点分析await()方法,这个方法是CyclicBarrier的核心方法。

// 方法声明抛出InterruptedException,表示可以相应中断
// BrokenBarrierException表示可以中断一次屏障,即打破CyclicBarrier中线程阻塞的状态,使其唤醒
    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 {
        // 定义了一个Generation内部类,表示一次循环
            final Generation g = generation;
		// 循环被打破,表示该CyclicBarrier不可用,抛出BrakenBarrierException异常
            if (g.broken)
                throw new BrokenBarrierException();

			// 响应线程中断
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }
			
			// 计数器减一,即表示一个线程任务到达。
            int index = --count;
            // 减为0后,即count==0,此时index==count,表示当前循环所有任务都到达,需唤醒阻塞的线程
            if (index == 0) {  // tripped
            
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    // 这里就是构造函数中设置的回调。不为null,则会执行回调,且只会由最后一个到达的线程执行。
                    if (command != null)
                        command.run();
                    // ranAction设置为true,表示需要唤醒阻塞的线程,继续往下执行,而不是阻塞,false表示需要打破这次循环
                    ranAction = true;
                    // 开启下一代,即下一轮循环:其实就是设置count重新等于parties
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }

            // loop until tripped, broken, interrupted, or timed out
            for (;;) {
                try {
                // 判断当前调用的是超时等待await(timeout,unit),还是一直等待await()
                    if (!timed)
                    // 阻塞当前线程,同时释放锁:此处的trip就是该Lock的Condition。对Condition不了解的同学可以看前面章节。
                        trip.await();
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    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)
                    throw new BrokenBarrierException();

                if (g != generation)
                    return index;

                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }
// 开始下一次循环:主要完成三件事:
// 1. trip.signalAll();唤醒所有阻塞在trip上的线程
// 2. count = parties;将需要等待的线程数重新设置回原来的parties值,以便开始下一次循环
// 3. generation = new Generation();开始下一次循环,broken默认为false
    private void nextGeneration() {
        // signal completion of last generation
        trip.signalAll();
        // set up next generation
        count = parties;
        generation = new Generation();
    }

// 打破循环,也主要完成三件事
// 1. generation.broken = true;将当前循环的broken设置为true,后续加入CyclicBarrier的线程,即执行await()方法时,会抛出异常
// 2. count = parties;恢复count的值,同上
// 3. trip.signalAll();唤醒所有阻塞的线程。
    private void breakBarrier() {
        generation.broken = true;
        count = parties;
        trip.signalAll();
    }

通过上述分析可知:

  1. 无论是调用nextGeneration()还是调用breakBarrier()都会唤醒当前阻塞的所有线程,只是前者会开启下一代,即开始下一轮循环等待。而后者不会开启下一代,且禁止线程再次加入CyclicBarrier的阻塞队列中来。
  2. CyclicBarrier是可重用的。等待线程到达后,会开启下一代,每一轮循环,被称为一个Generation,也就是一个同步点,一代。
  3. CyclicBarrier会响应中断。当线程没有到齐时,如果有线程接收到了中断信号,所有阻塞的线程都会被唤醒,且其他线程无法再加入
  4. CyclicBarrier可以指定回调函数,且回调函数只会被最后到达的线程执行,而不是每个线程都执行。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值