最常用的CountDownLatch, CyclicBarrier你知道多少?(必会知识)
cac55 2024-09-19 17:03 37 浏览 0 评论
CountdownLatch,CyclicBarrier是非常常用并发工具类,可以说是Java工程师必会技能了。不但在项目实战中经常涉及,而且在编写压测程序,多线程demo也是必不可少,所以掌握它们的用法和实现原理非常有必要。
等待多线程完成的CountDownLatch
CountDownLatch允许一个或多个线程等待其他线程完成操作。也就是说通过使用CountDownLatch工具类,可以让一组线程等待彼此执行完毕后在共同执行下一个操作。具体流程如下图所示,箭头表示任务,矩形表示栅栏,当三个任务都到达栅栏时,栅栏后wait的任务才开始执行。
CountDownLatch维护有个int型的状态码,每次调用countDown时状态值就会减1;调用wait方法的线程会阻塞,直到状态码为0时才会继续执行。
在多线程协同工作时,可能需要等待其他线程执行完毕之后,主线程才接着往下执行。首先我们可能会想到使用线程的join方法(调用join方法的线程优先执行,该线程执行完毕后才会执行其他线程),显然这是可以完成的。
使用Thread.join()方法实现
public class RunningRaceTest {
public static void main(String[] args) throws InterruptedException {
Thread runner1 = new Thread(new Runner(), "1号");
Thread runner2 = new Thread(new Runner(), "2号");
Thread runner3 = new Thread(new Runner(), "3号");
Thread runner4 = new Thread(new Runner(), "4号");
Thread runner5 = new Thread(new Runner(), "5号");
runner1.start();
runner2.start();
runner3.start();
runner4.start();
runner5.start();
runner1.join();
runner2.join();
runner3.join();
runner4.join();
runner5.join();
// 裁判等待5名选手准备完毕
System.out.println("裁判:比赛开始~~");
}
}
class Runner implements Runnable {
@Override
public void run() {
try {
int sleepMills = ThreadLocalRandom.current().nextInt(1000);
Thread.sleep(sleepMills);
System.out.println(Thread.currentThread().getName() + " 选手已就位, 准备共用时: " + sleepMills + "ms");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Thread.join()完全可以实现这个需求,不过存在一个问题,如果调用join的线程一直存活,则当前线程则需要一直等待。这显然不够灵活,并且当前线程可能会出现死等的情况。
更加灵活的CountDownLatch
jdk1.5之后的并发包中提供了CountDownLatch并发工具了,也可以实现join的功能,并且功能更加强大。
// 参赛选手线程
class Runner implements Runnable {
private CountDownLatch countdownLatch;
public Runner(CountDownLatch countdownLatch) {
this.countdownLatch = countdownLatch;
}
@Override
public void run() {
try {
int sleepMills = ThreadLocalRandom.current().nextInt(1000);
Thread.sleep(sleepMills);
System.out.println(Thread.currentThread().getName() + " 选手已就位, 准备共用时: " + sleepMills + "ms");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 准备完毕,举手示意
countdownLatch.countDown();
}
}
}
public class RunningRaceTest {
public static void main(String[] args) throws InterruptedException {
// 使用线程池的正确姿势
int size = 5;
AtomicInteger counter = new AtomicInteger();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(size, size, 1000, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), (r) -> new Thread(r, counter.addAndGet(1) + " 号 "), new ThreadPoolExecutor.AbortPolicy());
CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 0; i < size; i++) {
threadPoolExecutor.submit(new Runner(countDownLatch));
}
// 裁判等待5名选手准备完毕
countDownLatch.await(); // 为了避免死等,也可以添加超时时间
System.out.println("裁判:比赛开始~~");
threadPoolExecutor.shutdownNow();
}
}
输出结果:
5 号 选手已就位, 准备共用时: 20ms
4 号 选手已就位, 准备共用时: 156ms
1 号 选手已就位, 准备共用时: 288ms
2 号 选手已就位, 准备共用时: 519ms
3 号 选手已就位, 准备共用时: 945ms
比赛开始~~
同步屏障CyclicBarrier
CyclicBarrier可以实现CountDownLatch一样的功能,不同的是CountDownLatch属于一次性对象,声明后只能使用一次,而CyclicBarrier可以循环使用。
从字面意义上来看,CyclicBarrier表示循环的屏障,当一组线程全部都到达屏障时,屏障才会被移除,否则只能阻塞在屏障处。
public class RunningRace {
public static void main(String[] args) {
// 使用线程池的正确姿势
int size = 5;
AtomicInteger counter = new AtomicInteger();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(size, size, 1000, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), (r) -> new Thread(r, counter.addAndGet(1) + " 号 "), new ThreadPoolExecutor.AbortPolicy());
CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> System.out.println("裁判:比赛开始~~"));
for (int i = 0; i < 10; i++) {
threadPoolExecutor.submit(new Runner(cyclicBarrier));
}
}
}
class Runner implements Runnable {
private CyclicBarrier cyclicBarrier;
public Runner(CyclicBarrier countdownLatch) {
this.cyclicBarrier = countdownLatch;
}
@Override
public void run() {
try {
int sleepMills = ThreadLocalRandom.current().nextInt(1000);
Thread.sleep(sleepMills);
System.out.println(Thread.currentThread().getName() + " 选手已就位, 准备共用时: " + sleepMills + "ms" + cyclicBarrier.getNumberWaiting());
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
由于CyclicBarrier可以循环使用,所以CyclicBarrier的构造方法中可以传入一个Runnable参数,在每一轮执行完毕之后就会立刻执行这个Runnable任务。
CountDownLatch设计与实现
CountDownLath是基于AQS框架的一种简单实现,有两个核心的方法,即await()和countDown(),通过构造方法传入一个状态值,调用await()方法时线程会阻塞,直到状态码被修改成0时才会返回,每次调用countDown()时会将状态值减1。
wait方法:执行wait方法后,会尝试获取同步状态,如果为状态为0则方法继续执行,否择当前线程会被加入到同步队列中,详情可见笔者关于AQS的两篇文章。
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 如果状态码不为0,尝试获取同步状态,如果失败则被加入到同步队列中
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
// 当状态码为0时返回1,否择返回-1,这个方法中参数没有任何用处
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
countDown方法:每次执行countDown方法时,会将状态码的值减1.
public void countDown() {
sync.releaseShared(1);
}
CyclicBarrier的设计与实现
CyclicBarrier与CountDownLatch实现思想相同,也是基于AQS框架实现。不同的是CyclicBarrier内部维护一个状态值, 借助基于AQS实现的锁ReentrantLock来实现状态值的同步更新,以及AQS除了同步状态之外的另一个核心概念条件队列来完成线程的阻塞。
parties: 和CountdownLatch中的状态值一样,用来记录每次要相互等待的线程数量,只有parties个线程同时到达屏障时,才会唤醒阻塞的线程。
count临时计数器: 由于CyclicBarrier是可以循环使用的,count可以理解为是一个临时变量,每一轮执行完毕或者被打断都会重置count为parties值。
Generation内部类: 只有一个属性 broken表示当前这一轮执行是否被中断,如果被中断后其他线程再执行await方法会抛出异常(目的是停止本轮线程未执行线程的继续执行)。
await方法: 当执行await方法时,会同步得对内部的count执行--count操作, 如果count = 0,则执行barrierCommand任务(通过构造方法传来的Runnable参数)。
reset方法:中断本轮执行,重置count值,唤醒等待的线程然后开始下一轮,此时本轮正在执行的线程调用await方法会抛出异常。
// await方法实际执行的代码
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
// 加锁,保证并发操作的一致性
lock.lock();
try {
// 如果当前这一轮操作被中断,抛出中断异常(该异常只是起警示作用,没有任何其他信息)
final Generation g = generation;
if (g.broken)
throw new BrokenBarrierException();
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
// 本轮执行的计数器 数值-1
int index = --count;
if (index == 0) { // 计数器值=1, 本轮线程全部到达屏障,执行barrierCommand任务
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
nextGeneration();// 唤醒所有等待在条件队列上的任务
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// 如果状态不等于0,循环等待直到计数器值为0,本轮执行被打破,线程被中断,或者等待超时
for (;;) {
try {
if (!timed)
// 状态码不为0,将当前线程加入到条件队列中,进入阻塞状态
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();// 唤醒所有条件队列中的线程,重置count的值
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
重置栅栏的状态
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
breakBarrier(); // break the current generation
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
/**
* Sets current barrier generation as broken and wakes up everyone.
* Called only while holding lock.
*/
private void breakBarrier() {
generation.broken = true;
count = parties;
trip.signalAll();
}
当一轮执行完毕之后,既count=0后,CyclicBarrier的临时状态会重置为parties
/**
* 进入下一轮
* 唤醒所有等待线程,充值count
*/
private void nextGeneration() {
// signal completion of last generation
trip.signalAll();
// set up next generation
count = parties;
generation = new Generation();
}
总结
- CountDownLatch创建后只能使用一次,而CyclicBarrier可以循环使用,并且CyclicBarrier功能更完善。
- CountDownLatch内部的状态是基于AQS中的状态信息,而CyclicBarrier中的状态值是单独维护的,使用ReentrantLock加锁保证并发修改状态值的数据一致性。
- 它们的使用场景:允许一个或多个线程等待其他线程完成操作, 即当指定数量线程执行完某个操作再继续执行下一个操作。
笔者是95后java工程师,专注后端技术,小伙伴本可以关注wx公众号cruder,一起探讨学习和工作,一起进步!
相关推荐
- 苹果新macOS、新Mac还没出,但已经有新版虚拟机软件Parallels Desktop 19
-
自从苹果电脑全面转向ARM架构芯片之后,想在新款Mac电脑上安装Windows或Linux系统,就只能依靠虚拟机软件了,其中ParallelsDesktop应该是比较多Mac用户选择使用的一款,现在...
- 这个开源神器可快速帮你安装 MacOS 虚拟机
-
大家好,我是JackTian。安装Windows和Linux操作系统是最熟悉不过的必备技能了。那么,给大家推荐一个非常实用的开源脚本:macos-guest-virtualbox.sh,帮你...
- 如何在VMware虚拟机上安装运行Mac OS系统??
-
想在自己的Windows电脑上安装一个MacOS体验一下苹果系统的小伙伴,教程来了!!!一、安装前准备虚拟机运行软件:VMwareWorkstationPro,版本:16.0.0。(可以注册)VM...
- 效率!MacOS下超级好用的Linux虚拟工具:Lima
-
对于MacOS用户来说,搭建Linux虚拟环境一直是件让人头疼的事。无论是VirtualBox还是商业的VMware,都显得过于笨重且配置复杂。今天,我们要介绍一个轻巧方便的纯命令行Linux虚拟工具...
- 普通电脑安装苹果MacOS+Windows10双系统,这次可不是虚拟机
-
上篇文章中说到,有一朋友因为工作需要,得临时使用苹果系统,笔者给他用VmwareWorkStation安装了一个苹果系统的虚拟机,结果装是装上了,但是发现调整分辨率有点小问题,文件传输也不方便。虽说...
- 官方证实苹果M1芯片不支持Windows 11
-
中关村在线消息:近日根据微软官方透露,目前已经确定Windows11不支持运行在苹果M1芯片上,这意味着过往在Mac电脑上安装Windows系统的做法在M1芯片的Mac电脑上并不适用。不过此前有网友...
- 这可能是 Mac 共享文件最详细的教程了
-
如果希望让一台Mac访问另一台Mac上的文件,就可以使用Mac的文件共享功能。而且不仅是Mac之间,甚至用iPhone、iPad、WindowsPC都可以访问Mac的共享文件...
- 在 M1/M2 Mac 上,让 Windows 11 免费“跑”起来
-
自从苹果在产品中逐步使用自研的M系列芯片淘汰掉英特尔芯片之后,很多事情都发生了改变。作者|KirkMcElhearn和JoshuaLong译者|弯月出品|CSDN(ID:CS...
- VMware Workstation克隆虚拟机后修改ip地址和mac地址
-
VMwareWorkstation克隆虚拟机,登录之后发现,克隆虚拟机不仅用户名相同,连ip地址、mac地址也是相同的,很显然访问相同ip地址的虚拟机是会出现ip地址冲突的。一、修改IP地址这就需要...
- VirtualBox7中安装macOS big sur,在windows10&11上「保姆级教程」
-
macOSBigSur是苹果公司研发的桌面端操作系统,于北京时间2020年6月23日在2020苹果全球开发者大会上发布。BigSur采用全新的精美设计,为主要app如Safari浏览器...
- 最强mac虚拟机Parallels Desktop 16 有哪些重要的新增功能?
-
ParallelsDesktop16正式发布,软件带来了一些显着的新功能和性能增强,包括对macOSBigSur的全面支持。当苹果推出macOSBigSur时,它终止了对Par...
- 关于在MacOS安装虚拟机的全过程(macos 安装虚拟机)
-
哈喽大家好,我是咕噜美乐蒂,很高兴又见面啦!下面美乐蒂将详细地给大家介绍一下在macOS上使用VMwareFusion创建虚拟机并安装操作系统的步骤:一、确认虚拟化支持:首先,确认你的Ma...
- macOS上也能轻松运行Win系统的虚拟机,你还不知道吗?
-
在macOS系统上运行Win系统的方式,虚拟机篇吉安光头强原创你是否曾经为了在Mac上运行Windows系统而烦恼不用着急,下面我将分享一种简单易行的方法,让你轻松在Mac上运行Windows系统准备...
- Mac M芯片上安装统信UOS 1070arm64虚拟机
-
原文链接:MacM芯片上安装统信UOS1070arm64虚拟机Hello,大家好啊!今天给大家带来一篇关于如何在苹果M系列芯片的Mac电脑上,通过VMware安装ARM64版统信UOS1070...
- 虚拟机不好用?Mac mini 多配一台Windows电脑,用远程桌面更好!
-
最近新入手了MacminiM4款,这里来更新一下相关问题,对于还没有购买Macmini,但是又想要用苹果电脑的朋友,一些参考,我觉得还是挺有用的!Macmini选择哪个渠道购买好?现在比较划算...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 苹果新macOS、新Mac还没出,但已经有新版虚拟机软件Parallels Desktop 19
- 这个开源神器可快速帮你安装 MacOS 虚拟机
- 如何在VMware虚拟机上安装运行Mac OS系统??
- 效率!MacOS下超级好用的Linux虚拟工具:Lima
- 普通电脑安装苹果MacOS+Windows10双系统,这次可不是虚拟机
- 官方证实苹果M1芯片不支持Windows 11
- 这可能是 Mac 共享文件最详细的教程了
- 在 M1/M2 Mac 上,让 Windows 11 免费“跑”起来
- VMware Workstation克隆虚拟机后修改ip地址和mac地址
- VirtualBox7中安装macOS big sur,在windows10&11上「保姆级教程」
- 标签列表
-
- 如何绘制折线图 (52)
- javaabstract (48)
- 新浪微博头像 (53)
- grub4dos (66)
- s扫描器 (51)
- httpfile dll (48)
- ps实例教程 (55)
- taskmgr (51)
- s spline (61)
- vnc远程控制 (47)
- 数据丢失 (47)
- wbem (57)
- flac文件 (72)
- 网页制作基础教程 (53)
- 镜像文件刻录 (61)
- ug5 0软件免费下载 (78)
- debian下载 (53)
- ubuntu10 04 (60)
- web qq登录 (59)
- 笔记本变成无线路由 (52)
- flash player 11 4 (50)
- 右键菜单清理 (78)
- cuteftp 注册码 (57)
- ospf协议 (53)
- ms17 010 下载 (60)