Java并发编程三大神器之CyclicBarrier

1、CyclicBarrier是什么

CountDownLatch 有一个缺点是它的计数器只能够使用一次,也就是说当计数器(state)减到为 0 的时候,如果再有线程调用去 await() 方法,该线程会直接通过,不会再起到等待其他线程执行结果起到同步的作用。为了解决这个问题CyclicBarrier就应运而生了。

CyclicBarrier 主要作用其实和 CountDownLatch 差不多,都是让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障会被打开,所有被屏障阻塞的线程才会继续执行,不过它是可以循环执行的,这是它与 CountDownLatch 最大的不同。CountDownLatch 是只有当最后一个线程把计数器置为 0 的时候,其他阻塞的线程才会继续执行。
在这里插入图片描述

2、CyclicBarrier小试牛刀

比如我们打游戏每次进入下一关的时候都需要加载一些地图、背景音乐等,只有全部加载完了才能够进行游戏。
比如:有一个3关的游戏,而每关都需要加载地图数据、人物模型、背景音乐之后才可以游戏。

public class CyclicBarrierExample {
    public static void main(String[] args) {
        // parties – the number of threads that must invoke await before the barrier is tripped
        int parties = 3;

        // barrierAction – the command to execute when the barrier is tripped, or null if there is no action
        Runnable barrierAction = () -> System.out.println("本关卡所有的前置数据初始化完成,开始游戏... ...");

        CyclicBarrier cyclicBarrier = new CyclicBarrier(parties, barrierAction);
        new Thread(new PreTaskThread("加载地图数据", cyclicBarrier)).start();
        new Thread(new PreTaskThread("加载人物模型", cyclicBarrier)).start();
        new Thread(new PreTaskThread("加载背景音乐", cyclicBarrier)).start();
    }
}
public class PreTaskThread implements Runnable {
    private String task;
    private CyclicBarrier cyclicBarrier;

    public PreTaskThread(String task, CyclicBarrier cyclicBarrier) {
        this.task = task;
        this.cyclicBarrier = cyclicBarrier;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 3; i++) {
            Random random = new Random();
            try {
                Thread.sleep(random.nextInt(1000));
                System.out.println(String.format("关卡 %d 的任务 %s 完成", i, task));
                cyclicBarrier.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}

输出结果如下:
在这里插入图片描述

可以看到每关游戏开始都会等当前关卡把游戏的地图数据、人物模型,背景音乐初始化完成后才能开始进行游戏。并且还是可以循环控制的。

3、CyclicBarrier源码分析

3.1、变量结构组成

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

ReentrantLock lock:用于保护屏障入口的锁。
Condition trip:达到屏障并且不能放行的线程在trip条件变量上等待。
int parties:栅栏开启需要的到达线程总数。
Runnable barrierCommand:最后一个线程到达屏障后执行的回调任务。
Generation generation:CyclicBarrier 通过内部类 Generation 重复利用,每当 await() 达到最大次数的时候,就会重新 new 一个,表示进入了下一个轮回。里面只有一个boolean型属性,用来表示当前轮回是否有线程中断。

3.2、主要方法await()介绍

public int await() throws InterruptedException, BrokenBarrierException {
    try {
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}
/**
 * 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();
     try {
        //获取barrier当前的 “代”也就是当前循环
        final Generation g = generation;
        if (g.broken) throw new BrokenBarrierException();

        if (Thread.interrupted()) {
            breakBarrier();
            throw new InterruptedException();
        }
        // 每来一个线程调用await方法都会进行减1
        int index = --count;
        if (index == 0) {  // tripped
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;
                // new CyclicBarrier 传入 的barrierCommand, command.run()这个方法是同步的,如果耗时比较多的话,是否执行的时候需要考虑下是否异步来执行。
                if (command != null)
                    command.run();
                ranAction = true;
                // 这个方法1. 唤醒所有阻塞的线程,2. 重置下count(count 每来一个线程都会进行减1)和generation,以便于下次循环。
                nextGeneration();
                return 0;
            } finally {
                if (!ranAction)
                    breakBarrier();
            }
        }

        // loop until tripped, broken, interrupted, or timed out
        for (;;) {
            try {
                 // 进入if条件,说明是不带超时的await
                if (!timed)
                     // 当前线程会释放掉lock,然后进入到trip条件队列的尾部,然后挂起自己,等待被唤醒。
                    trip.await();
                else if (nanos > 0L)
                     //说明当前线程调用await方法时 是指定了 超时时间的!
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) {
                 //Node节点在 条件队列内 时 收到中断信号时 会抛出中断异常!
                //g == generation 成立,说明当前代并没有变化。
                //! g.broken 当前代如果没有被打破,那么当前线程就去打破,并且抛出异常..
                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.
                //执行到else有几种情况?
                //1.代发生了变化,这个时候就不需要抛出中断异常了,因为 代已经更新了,这里唤醒后就走正常逻辑了..只不过设置下 中断标记。
                //2.代没有发生变化,但是代被打破了,此时也不用返回中断异常,执行到下面的时候会抛出  brokenBarrier异常。也记录下中断标记位。
                    Thread.currentThread().interrupt();
                }
            }
			//唤醒后,执行到这里,有几种情况?
			//1.正常情况,当前barrier开启了新的一代(trip.signalAll())
			//2.当前Generation被打破,此时也会唤醒所有在trip上挂起的线程
			//3.当前线程trip中等待超时,然后主动转移到 阻塞队列 然后获取到锁 唤醒。
            if (g.broken) throw new BrokenBarrierException();
			//唤醒后,执行到这里,有几种情况?
			//1.正常情况,当前barrier开启了新的一代(trip.signalAll())
			//2.当前线程trip中等待超时,然后主动转移到 阻塞队列 然后获取到锁 唤醒。
            if (g != generation) return index;
			//唤醒后,执行到这里,有几种情况?
			//.当前线程trip中等待超时,然后主动转移到 阻塞队列 然后获取到锁 唤醒。
            if (timed && nanos <= 0L) {
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
         lock.unlock();
    }
}

CyclicBarrier 采用一个内部类 Generation 来维护当前循环,每一个 await 方法都会存储当前的generation,获取到相同generation对象的属于同一组,每当 count 的次数耗尽就会重新 new 一个 Generation 并且重新设置 count 的值为 parties,表示进入下一次新的循环。 从这个 await() 方法我们是不是可以知道只要有一个线程被中断了,当代的 generation 的 broken 就会被设置为 true,所以会导致其他的线程也会被抛出 BrokenBarrierException。相当于一个失败其他也必须失败,感觉有“强一致性“的味道。

4、CountDownLatch和CyclicBarrier对比总结

  • CountDownLatch 是为计数器是设置一个值,当多次执行 countdown 后,计数器减为 0 的时候所有线程被唤醒,然后 CountDownLatch 失效,只能够使用一次。
  • CyclicBarrier 是当 count 为 0 时同样唤醒全部线程,同时会重新设置 count 为 parties,重新 new 一个 generation 来实现重复利用。
  • 28
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值