阿里一面CyclicBarrier和CountDownLatch的区别是啥
引言
前面一篇文章我们《Java高并发编程基础三大利器之CountDownLatch》它有一个缺点,就是它的计数器只能够使用一次,也就是说当计数器(state
)减到为 0
的时候,如果 再有线程调用去 await
() 方法,该线程会直接通过,不会再起到等待其他线程执行结果起到同步的作用。为了解决这个问题CyclicBarrier
就应运而生了。
什么是CyclicBarrier
CyclicBarrier
是什么?把它拆开来翻译就是循环(Cycle
)和屏障(Barrier
)
它的主要作用其实和CountDownLanch
差不多,都是让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障会被打开,所有被屏障阻塞的线程才会继续执行,不过它是可以循环执行的,这是它与CountDownLanch
最大的不同。CountDownLanch
是只有当最后一个线程把计数器置为0
的时候,其他阻塞的线程才会继续执行。学习CyclicBarrier
之前建议先去看看这几篇文章:
如何使用
我们首先先来看下关于使用CyclicBarrier
的一个demo
:比如游戏中有个关卡的时候,每次进入下一关的时候都需要进行加载一些地图、特效背景音乐什么的只有全部加载完了才能够进行游戏:
/**demo 来源https://blog.csdn.net/lstcui/article/details/107389371
* 公众号【java金融】
*/
public class CyclicBarrierExample {
static 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 = 0; i < 4; 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();
}
}
}
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> {
System.out.println("本关卡所有的前置任务完成,开始游戏... ...");
});
new Thread(new PreTaskThread("加载地图数据", cyclicBarrier)).start();
new Thread(new PreTaskThread("加载人物模型", cyclicBarrier)).start();
new Thread(new PreTaskThread("加载背景音乐", cyclicBarrier)).start();
}
}
}
输出结果如下:
我们可以看到每次游戏开始都会等当前关卡把游戏的人物模型,地图数据、背景音乐加载完成后才会开始进行游戏。并且还是可以循环控制的。
源码分析
结构组成
/** 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();
- lock:用于保护屏障入口的锁
- trip :达到屏障并且不能放行的线程在trip条件变量上等待
- parties :栅栏开启需要的到达线程总数
- barrierCommand:最后一个线程到达屏障后执行的回调任务
- generation:这是一个内部类,通过它实现
CyclicBarrier
重复利用,每当await
达到最大次数的时候,就会重新new
一个,表示进入了下一个轮回。里面只有一个boolean
型属性,用来表示当前轮回是否有线程中断。
主要方法
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
可以进行循环计数?
CyclicBarrier
采用一个内部类Generation
来维护当前循环,每一个await
方法都会存储当前的generation
,获取到相同generation
对象的属于同一组,每当count
的次数耗尽就会重新new
一个Generation
并且重新设置count
的值为parties
,表示进入下一次新的循环。
从这个await
方法我们是不是可以知道只要有一个线程被中断了,当代的 generation
的broken
就会被设置为true
,所以会导致其他的线程也会被抛出BrokenBarrierException
。相当于一个失败其他也必须失败,感觉有“强一致性“的味道。
总结
CountDownLanch
是为计数器是设置一个值,当多次执行countdown
后,计数器减为0
的时候所有线程被唤醒,然后CountDownLanch
失效,只能够使用一次。CyclicBarrier
是当count
为0
时同样唤醒全部线程,同时会重新设置count
为parties
,重新new
一个generation
来实现重复利用。
结束
- 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
- 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
- 感谢您的阅读,十分欢迎并感谢您的关注。
巨人的肩膀摘苹果
https://javajr.cn/
http://www.360doc.com/content/20/0812/08/55930996_929792021.shtml
https://www.cnblogs.com/xxyyy/p/12958160.html
阿里一面CyclicBarrier和CountDownLatch的区别是啥的更多相关文章
- CyclicBarrier和CountDownLatch的差别
CyclicBarrier和CountDownLatch都用多个线程之间的同步,共同点:同时有N个线程在 CyclicBarrier(CountDownLatch) 等待上等待时,CyclicBarr ...
- Java并发之CyclicBarrier、CountDownLatch、Phaser
在Java多线程编程中,经常会需要我们控制并发流程,等其他线程执行完毕,或者分阶段执行.Java在1.5的juc中引入了CountDownLatch和CyclicBarrier,1.7中又引入了Pha ...
- 《java.util.concurrent 包源码阅读》21 CyclicBarrier和CountDownLatch
CyclicBarrier是一个用于线程同步的辅助类,它允许一组线程等待彼此,直到所有线程都到达集合点,然后执行某个设定的任务. 现实中有个很好的例子来形容:几个人约定了某个地方集中,然后一起出发去旅 ...
- 使用数据库乐观锁解决高并发秒杀问题,以及如何模拟高并发的场景,CyclicBarrier和CountDownLatch类的用法
数据库:mysql 数据库的乐观锁:一般通过数据表加version来实现,相对于悲观锁的话,更能省数据库性能,废话不多说,直接看代码 第一步: 建立数据库表: CREATE TABLE `skill_ ...
- JAVA多线程提高十:同步工具CyclicBarrier与CountDownLatch
今天继续学习其它的同步工具:CyclicBarrier与CountDownLatch 一.CyclicBarrier CyclicBarrier是一个同步辅助类,它允许一组线程互相等待,直到到达某个公 ...
- java多线程开发之CyclicBarrier,CountDownLatch
最近研究了一个别人的源码,其中用到多个线程并行操作一个文件,并且在所有线程全部结束后才进行主线程后面的处理. 其用到java.util.concurrent.CyclicBarrier 这个类. Cy ...
- JDK源码分析之concurrent包(四) -- CyclicBarrier与CountDownLatch
上一篇我们主要通过ExecutorCompletionService与FutureTask类的源码,对Future模型体系的原理做了了解,本篇开始解读concurrent包中的工具类的源码.首先来看两 ...
- CyclicBarrier及CountDownLatch的使用
CountDownLatch位于java.util.concurrent包下,是JDK1.5的并发包下的新特性. 首先根据Oracle的官方文档看看CountDownLatch的定义: A synch ...
- Android进阶——多线程系列之Semaphore、CyclicBarrier、CountDownLatch
今天向大家介绍的是多线程开发中的一些辅助类,他们的作用无非就是帮助我们让多个线程按照我们想要的执行顺序来执行.如果我们按照文字来理解Semaphore.CyclicBarrier.CountDownL ...
随机推荐
- SQLite在C#的使用
SQLite在C#的使用 http://www.cnblogs.com/SharkBin/archive/2012/11/03/2752277.html System.Data.SQLite.DLL的 ...
- Ajax & JSONP 原理
Ajax & JSONP 原理 AJAX不是JavaScript的规范,它只是一个哥们"发明"的缩写:Asynchronous JavaScript and XML,意思就 ...
- LeetCode 刷题 App / LeetCode 题解 App
LeetCode 刷题 APP / LeetCode 题解 App 全端支持 http://leetcode-app.xgqfrms.xyz/ http://leetcode-desktop.xgqf ...
- GitHub & GitHub Package Registry
GitHub & GitHub Package Registry npm https://github.blog/2019-05-10-introducing-github-package-r ...
- jsbridge 原理 & 通信原理
jsbridge 原理 & 通信原理 Hybrid 方案是基于 WebView 的,JavaScript 执行在 WebView 的 Webkit 引擎中; 因此,Hybrid 方案中 JSB ...
- 「NGK每日快讯」2021.1.14日NGK公链第72期官方快讯!
- java的单例模式小知识点
单例模式 目的 为了让一个类有且仅有一个实例 优点 只允许一个,节省空间 不用频繁创建删除,提高性能 缺点 不容易扩展 长期不使用会被系统当作垃圾回收,造成系统状态的丢失 实现 要点 防止外界随意的创 ...
- 用一次就会爱上的cli工具开发
本文转载自用一次就会爱上的cli工具开发 写在前面 最近接手任务--使用nodejs开发一个公司内部使用的cli工具,简而言之就是输入一行命令快速搭建好项目结构,也可以通过不同的命令引入不同的文件. ...
- 微信小程序(四)-样式 WXSS
样式 WXSS https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxss.html 1.尺寸单位 rpx(respons ...
- vscode的代码片段
一.快速创建一个vue单文件组件 "Create a new Component": { "prefix": "vue", "bo ...