CyclicBarrier循环屏障源码解析(基于jdk11)
CyclicBarrier循环屏障源码解析(基于jdk11)
1.1 CyclicBarrier概述
public class CyclicBarrier extends Object
CyclicBarrier被称为循环屏障/同步屏障,它可以使一定数量的线程反复在"屏障"上汇集,当线程达到"屏障"位置时将调用await(),这个方法将阻塞该线程直到所有线程都到达屏障位置。如果足够数量的线程都到达屏障位置,那么屏障将打开,此时所有的线程都将被唤醒进而释放执行,而屏障将被重置以便下次使用。
通过它可以实现让一组线程互相等待共同到达某个状态之后再全部同时执行,叫做“循环”是因为当足够数量的等待线程都被释放以后,CyclicBarrier可以被重复使用。
1.2 CyclicBarrier原理
1.2.1 基本结构(jdk11)
通过UML类图可知,CyclicBarrier是使用了ReentrantLock和Condition来完成屏障效果,本质上底层还是基于AQS的,只不过更加高级。
关键属性:
- parties:parties是在创建CyclicBarrier的时候指定的值,后续不可更改,表示屏障点数,或者说表示需要多少线程到达屏障(调用await)后,所有线程才会打破屏障继续往下运行
- count:count则初始化为parties的值,每当有一个线程到达屏障调用await方法之后,count就就递减1;当count 为0 时,表示所需要的所有线程都到了屏障,此时屏障可以被打破
变量parties始终用来记录所需总线程个数,而当count 值变为0后,又会将parties 的值赋给count,从而进行复用。使用两个变量的原因就是为了实现CyclicBarrier 的可复用性。
barrierCommand是一个任务,当所需要的线程都到达屏障后执行的回调任务。一个Generation内部类表示屏障的实现,generation属性表示当前屏障。
这里的lock锁用于控制线程并发的,保证代线程安全,await、reset、isBroken、getNumberWaiting方法都需要获取锁。
1.2.2 await()方法
public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException {
return dowait(true, unit.toNanos(timeout));
}
这个await不是Condition的方法。调用CyclicBarrier 的await方法表示当前线程到达屏障点。如果当前线程不是将到达的最后一个线程,当前线程将一直等待。
满足下面条件之一将会被唤醒,可能还会抛出异常:
- 所需的所有线程都调用了await()方法(正常结束);
- 其他某个线程中断当前线程,则当前线程被唤醒并且清除当前线程的已中断状态,随后将打破当前屏障,并唤醒其他线程,最后抛出InterruptedException;
- 其他某个线程中断其他等待的线程,则当前线程被唤醒并抛出InterruptedException;
- 其他线程调用reset()方法打破屏障并重置屏障,则当前线程被唤醒并抛出BrokenBarrierException;
- 其他线程在等待当前屏障时超时,则当前线程被唤醒并抛出 BrokenBarrierException;
- 最后一个线程在执行回调任务过程中发生异常,则当前线程被唤醒并抛出 BrokenBarrierException。
如果当前线程是最后一个将要到达的线程,则当前线程不会等待,并且如果构造方法中提供了一个非空的回调任务,那么在允许其他线程继续运行之前(唤醒其他等待的线程之前),当前线程将运行该任务。如果在执行回调任务过程中发生异常,则该异常将传播到当前线程中,将 barrier 置于损坏状态,最后当前线程抛出该异常。
内部调用了dowait核心方法。
1.2.3 dowait方法
dowait是CyclicBarrier的核心方法,完成各种判断逻辑,比如等待、唤醒机制。
大概步骤如下:
首先就是获取lock锁,保证线程安全。
final ReentrantLock lock = this.lock;
lock.lock();
在一个try块中。获取当前屏障,使用局部变量g保存;
如果当前屏障被打破了,那么直接抛出BrokenBarrierException异常
如果当前线程被中断了,那么调用breakBarrier打破当前屏障,随后抛出InterruptedException异常。
try {
final Generation g = generation; if (g.broken)
throw new BrokenBarrierException(); if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
...
count自减1,index记录到达的当前线程的索引,即自减1之后的count值;
如果index为0,那么表示当前线程是最后一个达到屏障的线程,所需要的所有线程都到达了屏障点:
- ranAction变量表示回调任务执行是否成功,初始化为false,表示执行失败;
- 开启一个try块:
- command变量记录回调任务;
- 如果command不为null,那么在当前线程(最后一个达到屏障的线程)中执行回调任务;
- 到这一步,表示command的执行没有抛出异常,那么ranAction设置为true;
- 调用nextGeneration重置屏障,并唤醒其他线程;
- 返回0,方法正常结束。
- 无论上面的有没有抛出异常(特指command任务的执行),都会执行finally代码块:
- 如果ranAction为false,表示command执行抛出了异常。那么调用breakBarrier打破当前屏障,并唤醒其他线程,随后抛出遇到的异常。
- 到这一步,表示index不为0,那么表示当前线程不是最后一个达到屏障的线程,可能需要等待。开启一个死循环:
- 开启一个开启一个try块:
- 如果是非超时等待,那么调用trip.await(),当前线程在trip条件变量上等待,直到被中断或者被唤醒。
- 否则,就是超时等待。如果超时时间大于0。那么调用trip.awaitNanos(),当前线程在trip条件变量上超时等待最多nanos纳秒,直到被中断或者被唤醒或者超时等待完毕,返回nanos,表示剩余超时等待时间。
- 在catch块中尝试捕获InterruptedException,即线程中断异常,如果捕获成功:
- 如果屏障g还是当前屏障,并且g没有被打破
- 那么当前线程调用breakBarrier打破当前屏障,并唤醒其他线程,随后抛出该异常。
- 否则,表示一种极端情况,即当前线程因为被中断而唤醒,但是由于cpu轮换,或者锁已被其他线程获取,还没有来得及打破屏障。此时最后一个线程就调用nextGeneration重置屏障成功,或者屏障被其他线程打破,或者屏障被其他线程reset。
- 设置当前线程的中断状态。这种情况如果是屏障正常打破,那么不需要抛出异常,算作等待完成,而如果是其他情况将会在下面判断并抛出异常!
- 如果屏障g还是当前屏障,并且g没有被打破
- 开启一个开启一个try块:
- 到这一步,表示被唤醒或者超时时间到了,或者被中断但是其他线程更改了屏障设置的情况。如果屏障g被打破,那么抛出BrokenBarrierException异常。
- 如果屏障g不是当前屏障,说明最后一个线程已经到了,并且该屏障被打破并重置,返回index,正常结束。
- 如果是超时操作,并且等于超时时间小于等于0,那么说明是超时时间到了,并且该屏障还没有被打破。那么当前线程调用breakBarrier打破当前屏障,并唤醒其他线程,最后抛出TimeoutException异常。
- 到这里,说明屏障g既没有被打破也没有被替换,那么继续下一次循环,此时可能会继续等待。这种情况发生的概率很低,这种唤醒被称作“虚假唤醒”
- 最终需要在finally中释放lock锁。
1.2.3.1 breakBarrier打破屏障
在出现异常的时候调用的方法,且必须在获得锁之后才会调用。用于打破当前屏障,表明这个屏障已经失效了。主要做三件事:
- 打破当前屏障(broken设置为true);
- count重置为parties;
- 唤醒所有在trip条件变量上等待的线程。
1.2.3.2 nextGeneration重置屏障
在正常完成的时候调用的方法,且必须在获得锁之后才会调用。用于重置当前屏障,表明这个屏障已经使用完毕。主要做三件事:
- 唤醒所有在trip条件变量上等待的线程;
- count重置为parties;
- 重新初始化一个Generation对象,赋给generation,这就是下一个屏障。
可以看到,以前的屏障被第丢弃,但是并没有被打破(broken没有设置为true)。
1.2.4 await(timeout, unit)超时等待
public int await(long timeout,TimeUnit unit)
调用await方法表示当前线程到达屏障点。如果当前线程不是将到达的最后一个线程,当前线程将最多等待指定的超时时间。满足下面条件之一将会被唤醒,可能还会抛出异常:
与await()方法相似
1.2.5 reset重置屏障
将打破当前屏障并且重置新屏障。所有在屏障处等待的线程将会被唤醒并且抛出BrokenBarrierException。
实际上就是在获得锁之后连续调用breakBarrier和nextGeneration方法!
1.3 CyclicBarrier的总结
CyclicBarrier和之前学习CountDownLatch有些相似,但是又有区别:
- CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同。CountDownLatch一般用于某个或者某一批线程等待若干个其他线程执行完任务之后,它才执行;而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
- 同一个线程中调用多次CountDownLatch的countDown方法,计数器就会减去多次;而同一线程中调用多次cyclicBarrier的await方法,还是只会算作一条线程到达当前屏障,因为调用一次await之后就会等待,而在所有线程都到达屏障之后,屏障开放并且重置,后续的await方法将算作在新屏障上的等待!
- CountDownLatch的计数器只能使用一次,而CyclicBarrier的屏障可以使用reset()方法重置,也会自动重置,所以CyclicBarrier能处理更为复杂的业务场景。
- CountDownLatch是使用原始的AQS框架实现的,而CyclicBarrier使用的则是更加高级的组件ReentrantLock和Condition,但是追根溯源,这两个组件也是依赖AQS实现的。
CyclicBarrier循环屏障源码解析(基于jdk11)的更多相关文章
- String,StringBuffer和StringBuilder源码解析[基于JDK6]
最近指导几位新人,学习了一下String,StringBuffer和StringBuilder类,从反馈的结果来看,总体感觉学习的深度不够,没有读出东西.其实,JDK的源码是越读越有味的.下面总结一下 ...
- 【Java并发集合】ConcurrentHashMap源码解析基于JDK1.8
concurrentHashMap(基于jdk1.8) 类注释 所有的操作都是线程安全的,我们在使用时无需进行加锁. 多个线程同时进行put.remove等操作时并不会阻塞,可以同时进行,而HashT ...
- Spring源码解析-基于注解依赖注入
在spring2.5版本提供了注解的依赖注入功能,可以减少对xml配置. 主要使用的是 AnnotationConfigApplicationContext: 一个注解配置上下文 AutowiredA ...
- Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例
概要 前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类——LinkedList.和学习ArrayList一样,接下来呢,我们先对Linked ...
- Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例
概要 上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解Arra ...
- Java 集合系列06之 Vector详细介绍(源码解析)和使用示例
概要 学完ArrayList和LinkedList之后,我们接着学习Vector.学习方式还是和之前一样,先对Vector有个整体认识,然后再学习它的源码:最后再通过实例来学会使用它.第1部分 Vec ...
- Java 集合系列10之 HashMap详细介绍(源码解析)和使用示例
概要 这一章,我们对HashMap进行学习.我们先对HashMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashMap.内容包括:第1部分 HashMap介绍第2部分 HashMa ...
- Java 集合系列 09 HashMap详细介绍(源码解析)和使用示例
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- Java 集合系列 05 Vector详细介绍(源码解析)和使用示例
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
随机推荐
- 使用 systemd 定时器代替 cron 作业
转载自:https://mp.weixin.qq.com/s/HpDVp1sNYve8b7OdoHdGNw 创建一个定时器 首先,创建一个运行基础东西的简单的服务,例如 free 命令.举个例子,你可 ...
- MySQL学习(2)---MySQL数据类型
ps:此随笔基于mysql 5.7.*版本. 补充: UNSIGNED:所有整数类型都可以有一个可选(非标准)UNSIGNED属性.无符号类型可用于在列中仅允许非负数存在,或者当开发者需要该列的较大数 ...
- 《Generative Adversarial Networks for Hyperspectral Image Classification 》论文笔记
论文题目:<Generative Adversarial Networks for Hyperspectral Image Classification> 论文作者:Lin Zhu, Yu ...
- 洛谷P1884 [USACO12FEB]Overplanting S (矩形切割)
一种矩形切割的做法: 1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long LL; 4 const in ...
- GC plan_phase二叉树挂接的一个算法
楔子 在看GC垃圾回收plan_phase的时候,发现了一段特殊的代码,仔细研究下得知,获取当前数字bit位里面为1的个数. 通过这个bit位为1的个数(count),来确定挂接当前二叉树子节点的一个 ...
- Vue 中为什么要有nextTick?
摘要:本文将浅析nextTick的作用.使用场景和背后的原理实现,希望对大家有所帮助. 本文分享自华为云社区<Vue 中的 nextTick 有什么作用?>,作者:CoderBin. 一. ...
- winscp报错Server sent passive reply with unroutable address. Using server address instead
找了一堆没用. 最后终于 1.使用winSCP连接ftp时,编辑会话,单击高级. 2.进入高级设置之后,单击连接,查看连接模式,把被动模式的勾,勾掉. 3.单击确定,然后保存配置,重新连接FTP,OK
- 在js中正则表达式验证小时分钟,将输入的字符串转换为对应的小时和分钟
文章目录 1.预备知识 2.在js中的代码片段 3.测试结果 1.预备知识 splict()方法 Date()的相关方法 setHours()的用法 2.在js中的代码片段 //验证小时和分钟 var ...
- 累加和为 K 的子数组问题
累加和为 K 的子数组问题 作者:Grey 原文地址: 博客园:累加和为 K 的子数组问题 CSDN:累加和为 K 的子数组问题 题目说明 数组全为正数,且每个数各不相同,求累加和为K的子数组组合有哪 ...
- Codeforces Round #829 (Div. 1/Div. 2) 1753 A B C D 题解
Div1A / 2C. Make Nonzero Sum 令最后每个\(a_i\)的系数为\(c_i\)(\(c_i=1/-1\)),发现只要满足\(c_1=1\)(下标从1开始),且c中没有两个-1 ...