本来想将 CyclicBarrier 的内容放到下一个章节,但是 CountDownLatch 的内容着实有些少,不够解渴,另外有对比才有伤害,所以内容没结束,咱得继续看 CyclicBarrier)
CyclicBarrier上面简单说了一下 CyclicBarrier 被创造出来的理由,这里先看一下它的字面解释:
概念总是有些抽象,我们将上面的例子用 CyclicBarrier 再做个改动,先让大家有个直观的使用概念
@Slf4j public class CyclicBarrierExample { // 创建 CyclicBarrier 实例,计数器的值设置为2 private static CyclicBarrier cyclicBarrier = new CyclicBarrier(2); public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(2); int breakCount = 0; // 将线程提交到线程池 executorService.submit(() -> { try { log.info(Thread.currentThread() + "第一回合"); Thread.sleep(1000); cyclicBarrier.await(); log.info(Thread.currentThread() + "第二回合"); Thread.sleep(2000); cyclicBarrier.await(); log.info(Thread.currentThread() + "第三回合"); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } }); executorService.submit(() -> { try { log.info(Thread.currentThread() + "第一回合"); Thread.sleep(2000); cyclicBarrier.await(); log.info(Thread.currentThread() + "第二回合"); Thread.sleep(1000); cyclicBarrier.await(); log.info(Thread.currentThread() + "第三回合"); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } }); executorService.shutdown(); } }运行结果:
结合程序代码与运行结果,我们可以看出,子线程执行完第一回合后(执行回合所需时间不同),都会调用 await() 方法,等所有线程都到达屏障点后,会突破屏障继而执行第二回合,同样的道理最终到达第三回合
形象化的展示上述示例的运行过程
看到这里,你应该明白 CyclicBarrier 的基本用法,但随之你内心也应该有了一些疑问:
怎么判断所有线程都到达屏障点的?
突破某一屏障后,又是怎么重置 CyclicBarrier 计数器,等待线程再一次突破屏障呢?
带着这些问题我们来看一看源码
源码分析同样先打开 CyclicBarrier 的类结构,展开类全部内容,其实也没多少内容
从类结构中看到有:
await() 方法,猜测应该和 CountDownLatch 是类似的,都是获取同步状态,阻塞自己
ReentrantLock,CyclicBarrier 内部竟然也用到了我们之前讲过的 ReentrantLock,猜测这个锁一定保护 CyclicBarrier 的某个变量,那肯定也是基于 AQS 相关知识了
Condition,存在条件,猜测会有等待/通知机制的运用
我们继续带着这些猜测,结合上面的实例代码一点点来验证
// 创建 CyclicBarrier 实例,计数器的值设置为2 private static CyclicBarrier cyclicBarrier = new CyclicBarrier(2);查看构造函数 (这里的英文注释舍不得删掉,因为说的太清楚了,我来结合注释来说明一下):
private final int parties; private int count; public CyclicBarrier(int parties) { this(parties, null); } /** * Creates a new {@code CyclicBarrier} that will trip when the * given number of parties (threads) are waiting upon it, and which * will execute the given barrier action when the barrier is tripped, * performed by the last thread entering the barrier. * * @param parties the number of threads that must invoke {@link #await} * before the barrier is tripped * @param barrierAction the command to execute when the barrier is * tripped, or {@code null} if there is no action * @throws IllegalArgumentException if {@code parties} is less than 1 */ public CyclicBarrier(int parties, Runnable barrierAction) { if (parties <= 0) throw new IllegalArgumentException(); this.parties = parties; this.count = parties; this.barrierCommand = barrierAction; }根据注释说明,parties 代表冲破屏障之前要触发的线程总数,count 本身又是计数器,那问题来了
直接就用 count 不就可以了嘛?为啥同样用于初始化计数器,要维护两个变量呢?