CyclicBarrier是如何成为一个"栅栏"的
CyclicBarrier
是一种类似于栅栏的存在,意思就是在栅栏开放之前你都只能被挡在栅栏的一侧,当栅栏移除之后,之前被挡在一侧的多个对象则同时开始动起来。
1. 如何使用CyclicBarrier
在介绍其原理之前,先了解一下CyclicBarrier
应该如何使用。
假设现在有这样的场景,我们需要开一个会议,需要张1、张2、张3三个人参加,
会议需要三个人都到齐之后才能开始,否则只能干等着;这个场景用CyclicBarrier
可以很契合的模拟出来。代码如下:
public static void main(String[] args) {
// 线程池,每个线程代表一个人
ThreadPoolExecutor executor = ThreadPoolProvider.getInstance();
// 会议所需的人数为3
CyclicBarrier barrier = new CyclicBarrier(3);
executor.execute(() -> {
try {
System.err.println("张1到达会议室");
barrier.await();
System.err.println("会议开始,张1开始发言");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
executor.execute(() -> {
try {
System.err.println("张2到达会议室");
barrier.await();
System.err.println("会议开始,张2开始发言");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
executor.execute(() -> {
try {
System.err.println("张3先去个厕所,内急解决再去开会");
TimeUnit.SECONDS.sleep(1);
System.err.println("张3到达会议室");
barrier.await();
System.err.println("会议开始,张3开始发言");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
executor.shutdown();
}
结果图:
通过上方代码可以知道CyclicBarrier
的几点:
- 使用
await()
来表示完成了某些事情。(上方例子的表现为到达了会议室) - 使用
await()
之后当前线程就进入阻塞状态,需要等待完全满足CyclicBarrier
的条件后唤醒才能继续接下来的操作。(上方例子中 为3个人都到达会议室) - 在最后一个线程达到条件之后,之前阻塞的线程全部放开,继续接下来的操作。(上方例子为张3到达会议室)
这个简单的例子也让我们了解CyclicBarrier
的使用方法,那来看看其内部究竟是如何实现栅栏的效果的。
2. CyclicBarrier是如何成为"栅栏"的
从第一节的代码中我们也能看到,需要关注的就两个地方
- 构造函数
- await()方法
只要了解这两个方法的内部,相当于了解了CyclicBarrier
的内部。
那在深入了解之前,先来看下CyclicBarrier
的几个变量,不用刻意去记,看代码的时候知道这个东西做什么用的就行了:
lock:
CyclicBarrier
类创建的ReentrantLock
实例,关于ReentrantLock
不清楚的可以->传送。trip:
lock
中的condition
,CyclicBarrier
使用该变量来实现各线程之间的阻塞和同时唤醒。同样,不明白condition
作用的=>传送门。parties:需要满足条件(调用
await
方法)的总数,就是说当有parties个线程await()之后就会唤醒全部线程。barrierCommand:一个
Runnable
变量,在await
方法的调用次数到达总数parties
之后,在唤醒全部线程之前执行其run()
方法generation:其内部类,可以理解为周期,周期内需要完成n个任务,只要一个任务失败,当前周期的所有任务就算失败,结束当前周期,再开启下个周期。
count:当前周期剩余需要完成的任务数(剩余调用
await
方法的次数)
以下为源码:
public class CyclicBarrier {
// 内部类,可理解为周期
private static class Generation {
// 当前周期是否失败
boolean broken = false;
}
// 锁的实例
private final ReentrantLock lock = new ReentrantLock();
// ReentrantLock的condition变量,用来控制线程唤醒和阻塞
private final Condition trip = lock.newCondition();
// 需要满足条件的次数,即需要调用await方法的次数
private final int parties;
// 满足条件次数达到parties之后,唤醒所有线程之前执行其 run()方法
private final Runnable barrierCommand;
// 当前周期
private Generation generation = new Generation();
// 剩余满足条件次数
private int count;
// ...
}
看完CyclicBarrier
的几个变量后,来看其具体的内部实现。
首先来看构造函数,其构造函数有两个,一个在达到条件总数(parties)后直接叫醒所有线程;另一个指定一个Runnable
在达到条件总数后先执行其run()方法再叫醒。
- 不指定
Runnable
,参数只有一个:需要达成的任务数
public CyclicBarrier(int parties) {
// 直接调用另一个构造方法,Runnable传null,表示不执行
this(parties, null);
}
- 指定
Runnable
的构造方法,赋值任务总数、剩余任务数、唤醒操作之前的Runnable
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
// 任务总数
this.parties = parties;
// 剩余需要完成的任务数
this.count = parties;
// 唤醒之前执行的Runnable
this.barrierCommand = barrierAction;
}
在第一节我们使用的是第一个构造方法,来试试第二个
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor executor = ThreadPoolProvider.getInstance();
/** =======增加Runnable,其他地方保持一致=============*/
CyclicBarrier barrier = new CyclicBarrier(3, ()-> System.err.println("在会议开始之前,先给大家发下开会资料"));
executor.execute(() -> {
try {
System.err.println("张1到达会议室");
barrier.await();
System.err.println("会议开始,张1开始发言");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
executor.execute(() -> {
try {
System.err.println("张2到达会议室");
barrier.await();
System.err.println("会议开始,张2开始发言");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
executor.execute(() -> {
try {
System.err.println("张3先去个厕所,内急解决再去开会");
TimeUnit.SECONDS.sleep(1);
System.err.println("张3到达会议室");
barrier.await();
System.err.println("会议开始,张3开始发言");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
executor.shutdown();
}
结果图:
看完构造函数,就算理解了一半CyclicBarrier
了,接下来来看另一半——await()
;跟踪代码,看到是这样的
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
直接调用dowait
方法,传参为false
和0,意思就是不限时等待,除非线程被打断或者唤醒。再进入dowait
方法,这个方法就是CyclicBarrier
的另一半,在下方的代码中很清楚的写了整个执行流程
/** 参数说明, timed:是否限时, nanos:限时时间*/
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException, TimeoutException {
// 锁
final ReentrantLock lock = this.lock;
// 获取锁,如果失败的话线程睡眠,进入同步队列(AQS中的知识)
lock.lock();
try {
/* 拿到锁之后进入代码处理逻辑*/
// 当前周期
final Generation g = generation;
// 如果当前周期是失败的,那么直接抛错
if (g.broken)
throw new BrokenBarrierException();
// 如果当前线程被打断了,那么此次周期失败,设置相关参数,然后抛错
if (Thread.interrupted()) {
// 实现代码在下行的注释中,设置相关参数来提醒其他线程周期失败了
breakBarrier();
/*
* private void breakBarrier() {
* generation.broken = true;
* count = parties;
* // 唤醒condition中的所有线程
* trip.signalAll();
* }
*/
throw new InterruptedException();
}
// 如果成功了,那么剩余任务数(count)减1
int index = --count;
// 如果为0则表示达到剩余的任务数没有了,达到CyclicBarrier的条件总数了,需要唤醒其他线程
if (index == 0) {
boolean ranAction = false;
try {
// 唤醒之前的Runnable
final Runnable command = barrierCommand;
// 如果不为空的话执行其run方法
if (command != null)
command.run();
ranAction = true;
// 开启下个周期,这个方法是CyclicBarrier可以复用的原因,具体实现在下行注释
nextGeneration();
/* private void nextGeneration() {
* // 首先叫醒当前周期的其他线程,告诉其周期结束了,可以执行接下来的操作了
* trip.signalAll();
* // 然后开启下个周期,剩余任务数重置
* count = parties;
* // 下个周期
* generation = new Generation();
* }
*/
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// 如果还不能结束本周期,就一直等待直到结束或者周期失败
for (;;) {
try {
// await的过程中是释放锁的
// 不限时的话就一直等待直到被唤醒或者打断
if (!timed)
trip.await();
else if (nanos > 0L)
// 否则的话等待一段时间后醒来
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
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.
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();
}
}
到这里就基本理解CyclicBarrier
的内部实现了,其他像带参数的await
也是一样逻辑,只不过是多了限时的条件而已。
其实如果你了解ReentrantLock
的话,就知道CyclicBarrier
整个就是对ReentrantLock
的condition
的活用而已。
3.总结
整体来说CyclicBarrier
的实现相对较简单,说是ReentrantLock
中condition
的升级版也不为过。其关键点为两个,一个为其构造函数,决定任务个数和唤醒前操作;另外一个点为await
方法,在正常情况下每次await
都会减少一个任务数(总数由构造方法决定),在任务数变为0的时候表示周期结束,需要唤醒condition
的其他线程,而途中遇到失败的话当前周期失败,唤醒其他线程一起抛错。
失败不会让你变得弱小,害怕失败会。
CyclicBarrier是如何成为一个"栅栏"的的更多相关文章
- 并发编程(七)——AbstractQueuedSynchronizer 之 CountDownLatch、CyclicBarrier、Semaphore 源码分析
这篇,我们的关注点是 AQS 最后的部分,共享模式的使用.本文先用 CountDownLatch 将共享模式说清楚,然后顺着把其他 AQS 相关的类 CyclicBarrier.Semaphore 的 ...
- CyclicBarrier 原理(秒懂)
疯狂创客圈 经典图书 : <Netty Zookeeper Redis 高并发实战> 面试必备 + 面试必备 + 面试必备 [博客园总入口 ] 疯狂创客圈 经典图书 : <Sprin ...
- java 并发(五)---AbstractQueuedSynchronizer(3)
文章代码分析和部分图片来自参考文章 问题 : CountDownLatch 和 CyclicBarrier 的区别 认识 CountDownLatch 分析这个类,首先了解一下它所可以 ...
- Java并发指南9:AQS共享模式与并发工具类的实现
一行一行源码分析清楚 AbstractQueuedSynchronizer (三) 转自:https://javadoop.com/post/AbstractQueuedSynchronizer-3 ...
- 栅栏 CyclicBarrier
java.util.concurrent.CyclicBarrier 类是一种同步机制,它能够对处理一些算法的线程实现同步.换句话讲,它就是一个所有线程必须等待的一个栅栏,直到所有线程都到达这里,然后 ...
- 戏说java多线程之CyclicBarrier(循环栅栏)的CyclicBarrier(int parties)构造方法
CyclicBarrier是JDK 1.5 concurrent包出现的一个用于解决多条线程阻塞,当达到一定条件时一起放行的一个类.我们先来看这样一个简单的需求. 现在我有一个写入数据的类,继承Run ...
- Java并发编程原理与实战二十七:循环栅栏:CyclicBarrier
昨天我们学习了倒计数功能的等待,今天我们学习的是循环栅栏:CyclicBarrier.下面我们就开始吧: 1.CyclicBarrier简介CyclicBarrier,是JDK1.5的java.uti ...
- 同步机制之--java CyclicBarrier 循环栅栏
CyclicBarrier介绍一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point).在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待 ...
- 并发编程-concurrent指南-回环栅栏CyclicBarrier
字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行. java.util.concurrent.CyclicBarrier 类是一种同步机制,它能够对处理一些算法的线程实现同步 ...
随机推荐
- linux构建DHCP服务器
1.DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)是一个局域网的网络协议,使用UDP协议工作,主要用途:给内部网络或网络服务供应商自动分配IP地址 ...
- Java原来还可以这么学:如何搞定面试中必考的集合类
原创声明 本文作者:黄小斜 转载请务必在文章开头注明出处和作者. 系列文章介绍 本文是<五分钟学Java>系列文章的一篇 本系列文章主要围绕Java程序员必须掌握的核心技能,结合我个人三年 ...
- python社区要放弃了pip?版本信息里带警告很不寻常哦
pip是python的一个包管理器. 今天再查询Pip3 -V 时,除了正常的版本信息外,多了几行信息 WARNING: pip is being invoked by an old script w ...
- SpringBoot是如何实现自动配置的?--SpringBoot源码(四)
注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 前言 本篇接 助力SpringBoot自动配置的条件注解ConditionalOnXXX分析--SpringBoot源码(三 ...
- Python 装饰器(无参,有参、多重))
Python装饰器介绍 在Python中,装饰器(decorator)是在闭包的基础上发展起来的. 装饰器的实质是一个高阶函数,其参数是要装饰的函数名,其返回值是完成装饰的函数名,其作用是为已经存在的 ...
- js 实现字符串的查找和替换
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- ubuntu下使用apt-get install安装软件的安装位置
在ubuntu下使用 apt-get install 或 apt install 下载安装软件,软件下载及安装后的目录.: A.下载的软件的存放位置:/var/cache/apt/archives B ...
- 6.前台项目vue环境、创建、目录重构、CSS、JS配置
目录 前台 vue环境 创建项目 重构项目目录 文件修订:目录中非配置文件的多余文件可以移除 App.vue router/index.js Home.vue 全局配置:全局样式.配置文件 globa ...
- Java对接百度智能云人脸识别
------------------------->这篇文章就是自己做个笔记<------------------------- 首先登录or注册自己的百度智能云管理中心:https:// ...
- BJDCTF
python3的模板注入 非常简单...就是直接执行命令就行..虽然过滤了flag,但是拼接下就好了.... payload: http://fd5883ee-b8e2-4bf1-88af-33936 ...