CountDownLatch与CyclicBarrier原理剖析

news/2024/4/19 17:52:07/文章来源:https://blog.csdn.net/qq_52002412/article/details/129225114

1.CountDownLatch

1.1 什么是CountDownLatch

CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。

CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成一些任务,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。

1.2 CountDownLatch与join

使用join同样可以达到线程同步的效果,但是调用thread.join() 方法必须等thread 执行完毕,当前线程才能继续往下执行,而CountDownLatch通过计数器提供了更灵活的控制,只要检测到计数器为0当前线程就可以往下执行而不用管相应的thread是否执行完毕。

而且如果我们使用线程池的话,就没有办法直接调用线程的join方法了。所有另一方面来说,CountDownLatch要比join更加灵活。

1.3 CountDownLatch的基本使用

public class CountDownLatchTest {public static void main(String[] args) throws InterruptedException {CountDownLatch latch = new CountDownLatch(5);for (int i=0; i<=5; i++) {new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " 运行");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();} finally {latch.countDown();}}}).start();}System.out.println("等待子线程运行结束");latch.await();System.out.println("子线程运行结束");}
}

这里主线程会阻塞在latch.await(),直到CountDownLatch技术为0。

1.4 CountDownLatch原理剖析

CountDownLatch类图

请添加图片描述

从上面我们可以直到Sync继承了AQS类,CountDownLatch又持有一个成员变量Sync,所有我们可以直到CountDownLatch是基于AQS实现的。

通过构造方法我们又可以得知CountDownLatch计数器的值赋给了AQS的state变量。

public CountDownLatch(int count) {if (count < 0) throw new IllegalArgumentException("count < 0");this.sync = new Sync(count);
}Sync(int count) {setState(count);
}

await()

当线程调用await方法以后,当前线程会被阻塞,直到下面情况之一时才会返回:

  • 当所有的线程都调用了CountDownLatchcountDown方法后,也就是计数器为0时
  • 其他线程调用了当前线程的interrupt()方法中断了当前线程,当前线程会抛出异常然后返回
// CountDownLatch的await()实现
public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);
}// AQS中获取共享资源可被中断的方法
public final void acquireSharedInterruptibly(int arg)throws InterruptedException {// 判断当前线程是否已被中断,如果是则抛出异常if (Thread.interrupted())throw new InterruptedException();// state不为0(意味着CountDownLatch还没有减到0)// 则执行AQS的doAcquireSharedInterruptibly方法,让当前线程进入AQS队列if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);
}// CountDownLatch中的Sync中tryAcquireShared()方法
protected int tryAcquireShared(int acquires) {// 当前state如果为0则返回1否则返回-1return (getState() == 0) ? 1 : -1;
}

由上述代码我们可以知道线程获取资源时可以被中断,并且获取的是共享资源。

名为await的方法还有一个,不过多个参数,也就是指定时间后,调用await(long timeout, TimeUnit unit)的线程会超时而返回false,如果是正常返回的,那么返回值就为true。

public boolean await(long timeout, TimeUnit unit)throws InterruptedException {return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

countDown()

线程调用该方法以后,计数器的值会递减,递减后如果计数器的值为0,那么就会唤醒所有因调用await方法而阻塞的线程,否则什么都不做。

// CountDownLatch中的countDown方法
public void countDown() {sync.releaseShared(1);
}// AQS中的方法
public final boolean releaseShared(int arg) {// 调用Sync实现的tryReleaseShared方法if (tryReleaseShared(arg)) {// AQS中释放资源的方法doReleaseShared();return true;}return false;
}// Sync中重写的tryReleaseShared方法
protected boolean tryReleaseShared(int releases) {// 循环进行CAS操作,将state做减一操作,失败则一直重试for (;;) {// 获得当前的state变量int c = getState();// 如果state已经等于0,那么直接返回falseif (c == 0)return false;// 将state-1int nextc = c-1;// CAS操作修改state,成功以后判断state是否已经为0,为0则返回true,// 再接下来就会调用AQS中的doReleaseShared方法释放资源if (compareAndSetState(c, nextc))return nextc == 0;}
}

2.CyclicBarrier

2.1 什么是CyclicBarrier

从字面理解,CyclicBarrier就是回环屏障的意思,它可以让一组线程达到一个状态后再同时执行。

  • 回环的意思是在所有线程执行完毕以后,会重置CyclicBarrier的状态使它可以被重用
  • 屏障的意思是线程调用await方法后都会被阻塞,这个阻塞点就被称为屏障,等到所有屏障都调用了await方法后,线程们就会冲破屏障继续向下运行

2.2 CyclicBarrier的基本使用

public class CycleBarrierTest {private static CyclicBarrier cyclicBarrier = new CyclicBarrier(2, new Runnable() {// 当计数器为0时,立即执行@Overridepublic void run() {System.out.println("汇总线程:" + Thread.currentThread().getName() + " 任务合并。");}});public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(2);// 将线程A添加到线程池executorService.submit(new Runnable() {@Overridepublic void run() {try {System.out.println("线程A:" + Thread.currentThread().getName() + "执行任务。");System.out.println("线程A:到达屏障点");cyclicBarrier.await();System.out.println("线程A:退出屏障点");} catch (Exception e) {e.printStackTrace();}}});// 将线程B添加到线程池executorService.submit(new Runnable() {@Overridepublic void run() {try {System.out.println("线程B:" + Thread.currentThread().getName() + "执行任务。");System.out.println("线程B:到达屏障点");cyclicBarrier.await();System.out.println("线程B:退出屏障点");} catch (Exception e) {e.printStackTrace();}}});// 调用线程池的shutdown方法关闭线程池// 该方法会使线程池从RUNNING状态转变为SHUTDOWN状态// SHUTDOWN状态意味着:不再接收新的任务,但是会对任务队列中的任务进行处理executorService.shutdown();}
}

执行结果为:

线程A:pool-1-thread-1执行任务。
线程A:到达屏障点
线程B:pool-1-thread-2执行任务。
线程B:到达屏障点
汇总线程:pool-1-thread-2 任务合并。
线程B:退出屏障点
线程A:退出屏障点

上面的例子说明了多个线程之间是相互等待的,假如计数器值为N,那么随后调用 await 方法的 N–1 个线程都会因为到达屏障点而被阻塞,当第 N 个线程调用 await 后,计 数器值为 0 了,这时候第 N 个线程才会发出通知唤醒前面的 N–1 个线程。也就是当全部 线程都到达屏障点时才能一块继续向下执行。不过这个例子并没有体现出可重用性,不过这个其实也很好理解,就是可以反复使用,感兴趣的同学可以自己去了解一下。

2.3 CyclicBarrier原理剖析

CyclicBarrier类图

请添加图片描述

由类图可知,CyclicBarrier基于独占锁实现,本质还是基于AQS实现的。

  • Generation内部有一个变量broken,用来记录当前屏障是否被打破,因为内部使用重入锁保证了线程安全,所以该属性不需要使用volatile修饰

  • parties用来记录线程个数,意味着parties个线程调用await方法以后,才会“冲破屏障

  • count一开始等于parties,每当有线程调用await方法就会减一,当count为零就意味着所有线程到达了屏障点

使用两个变量的原因就是为了达成CyclicBarrier的复用性,当count计数为0以后,会将parties重新赋值给count,从而进行复用。

public CyclicBarrier(int parties, Runnable barrierAction) {if (parties <= 0) throw new IllegalArgumentException();this.parties = parties;this.count = parties;this.barrierCommand = barrierAction;
}

同时通过构造函数我们也可以直到,我们可以传递一个任务,而这个任务的执行时机是当所有的线程都到达屏障点以后。

await()

当线程调用await方法被阻塞,直到满足以下条件之一时就会返回:

  • parties个线程调用了await方法,也就是所有线程都到达了屏障点
  • 其他线程调用了该线程的interrupt方法中断了该线程
  • 与当前屏障点关联的Generation对象的broken标志被设置为true时
public int await() throws InterruptedException, BrokenBarrierException {try {// 调用dowait方法阻塞当前线程,第一个参数为false表示第二个参数不生效return dowait(false, 0L);} catch (TimeoutException toe) {throw new Error(toe); // cannot happen}
}public int await(long timeout, TimeUnit unit)throws InterruptedException,BrokenBarrierException,TimeoutException {// 指定timeout后会自动返回return dowait(true, unit.toNanos(timeout));
}

dowait()

该方法实现了CyclicBarrier的核心功能。

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();}// 如果index=0就说明所有的线程都到达了屏障点,此时开始执行初始化传递的任务barrierActionint index = --count;if (index == 0) { boolean ranAction = false;try {final Runnable command = barrierCommand;// 执行任务if (command != null)command.run();ranAction = true;// 激活其他因调用await阻塞的线程,并且重置了CyclicBarriernextGeneration();return 0;} finally {if (!ranAction)breakBarrier();}}// 如果index!=0for (;;) {try {// 没有设置超时时间的操作if (!timed)trip.await();// 设置了超时时间的操作else if (nanos > 0L)nanos = trip.awaitNanos(nanos);} catch (InterruptedException ie) {if (g == generation && ! g.broken) {breakBarrier();throw ie;} else {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可以被复用
private void nextGeneration() {// 唤醒条件队列中的阻塞线程trip.signalAll();// 重置CyclicBarriercount = parties;generation = new Generation();
}

3.CountDownLatch与CyclicBarrier

  • CountDownLatch计数器不能重置,CyclicBarrier可以重置循环利用。
  • CountDownLatch是基于AQS的共享模式实现的,CyclicBarrier是基于ReentrantLockCondition实现的。
  • 两者最大的区别是,进行下一步动作的动作实施者是不一样的。这里的“动作实施者”有两种,一种是主线程(即执行main函数),另一种是执行任务的其他线程,后面叫这种线程为“其他线程”,区分于主线程。对于CountDownLatch,当计数为0的时候,下一步的动作实施者是main函数;对于CyclicBarrier,下一步动作实施者是“其他线程”。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_74458.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学习网安需要了解的一些基础知识

P1.基本概念 1.POC/EXP POC(proof of concept)常指一段漏洞验证代码&#xff1b;EXP(exploit)指利用系统漏洞进行攻击的动作 PoC是证明漏洞存在的,而 Exp 是利用这个漏洞进一步进行攻击&#xff0c;先有POC&#xff0c;才有EXP 2.Payload/shellcode payload&#xff0…

学习周报2.26

文章目录前言文献阅读摘要方法结果深度学习Encoder-Decoder&#xff08;编码-解码&#xff09;信息丢失的问题Attention机制总结前言 This week,I read an article about daily streamflow prediction.This study shows the results of an in-depth comparison between two di…

Lambda表达式的本质

一直想写一篇文章&#xff0c;来总结lambda表达式&#xff0c;但是之前感觉总结的不是特别到位&#xff0c;现在看了几篇文章和视频后&#xff0c;感觉对lambda表达式有了比较深刻的认识&#xff0c;现在进行记录总结如下&#xff1a; lambda表达式又叫做匿名函数&#xff0c;…

网络应用之HTTP响应报文

HTTP响应报文学习目标能够知道HTTP响应报文的结构1. HTTP响应报文分析HTTP 响应报文效果图:响应报文说明:--- 响应行/状态行 --- HTTP/1.1 200 OK # HTTP协议版本 状态码 状态描述 --- 响应头 --- Server: Tengine # 服务器名称 Content-Type: text/html; charsetUTF-8 # 内容类…

【教程】Notion笔记多平台设置中文显示

这个笔记软件界面挺好看&#xff0c;惊艳到了。 目录 网页版 桌面端 Windows版 Mac端 安卓端 网页版 直接安装这个插件即可&#xff0c;Chrome/Edge适用&#xff1a;Notion中文版 桌面端 都要去这个github下载语言包&#xff0c;用于替换文件&#xff1a;https://github.c…

xxjob分布式任务调度

前言 在工作中使用到了定时任务,通过查找资料选择了xxjob,以下是xxjob的介绍以及基本的使用. xxjob介绍 XXL-JOB是一个分布式任务调度平台&#xff0c;其核心设计目标是开发迅速、学习简单、轻量级、易扩展。 将调度行为抽象形成“调度中心”公共平台&#xff0c;而平台自身…

OpenCV-Python系列(二)—— 图像处理(灰度图、二值化、边缘检测、高斯模糊、轮廓检测)

一、【灰度图、二值化】 import cv2 img cv2.imread("lz2.png") gray_img cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 灰度图 # 二值化&#xff0c;(127,255)为阈值 retval,bit_img cv2.threshold(gray_img, 127, 255, cv2.THRESH_BINARY) cv2.imshow(photo1,im…

Laravel框架03:DB类操作数据库

Laravel框架03&#xff1a;DB类操作数据库一、概述二、数据表的创建与配置三、增删改操作1. 增加信息2. 修改数据3. 删除数据四、查询操作1. 取出基本数据2. 取出单行数据3. 获取一个字段的值4. 获取多个字段的值5. 排序6. 分页五、执行任意的SQL语句一、概述 按照MVC的架构&a…

【R统计】R语言相关性分析及其可视化

&#x1f482; 个人信息&#xff1a;酷在前行&#x1f44d; 版权: 博文由【酷在前行】原创、需要转载请联系博主&#x1f440; 如果博文对您有帮助&#xff0c;欢迎点赞、关注、收藏 订阅专栏&#x1f516; 本文收录于【R统计】&#xff0c;该专栏主要介绍R语言实现统计分析的…

浅谈QWebChannel、QWebChannelAbstractTransport、QWebSocketServer、QWebSocket用法及之间关系

1.前言在现实业务中&#xff0c;经常遇到这样的需求&#xff1a;一端采用web形式开发的&#xff0c;如&#xff1a;客户端采用html、javascript、nodejs开发&#xff1b;而另一端采用C开发&#xff0c;如&#xff1a;Qt开发的服务端。web页面端需和Qt开发的服务端进行通信、数据…

房屋出租管理系统

1. 铺垫 1.1 项目真实开发的过程 上来要做什么&#xff1f;&#xff1f;&#xff1f;&#xff1f; 有电脑—》配环境&#xff08;JDK、IDEA、MAVEN……&#xff09; 这个项目&#xff1a;房屋管理系统 从什么角度出发&#xff0c;第一步做什么&#xff1f;&#xff1f; 架构 …

IoT项目系统架构案例2

项目背景 1.这个项目是对之前的案例的升级改造参考&#xff1a;IoT项目系统架构案例_iot案例_wxgnolux的博客-CSDN博客2.基于方案1的项目实施过程中碰到的问题,对硬件设备标准化的理念及新的功能需求(如根据天气预报温度调水温,APP界面可操作性优化等)•采用目前IoT主流厂商的架…

vue中render函数的作用和参数(vue2中render函数用法)

render 函数是 Vue2.x 新增的一个函数、主要用来提升节点的性能&#xff0c;它是基于 JavaScript 计算。使用 Render 函数将 Template 里面的节点解析成虚拟的 Dom 。Vue 推荐在绝大多数情况下使用模板来创建 HTML。然而在一些场景中&#xff0c;需要 JavaScript 的完全编程能力…

gitlab部署使用,jenkins部署使用

gitlab部署使用&#xff0c;jenkins部署使用gitlab下载gitlab安装gitlab使用gitlab设置中文修改管理员密码创建组,创建项目,创建用户jenkins下载jenkins安装jenkin使用jenkins更改管理员密码配置拉取代码配置登录gitlab拉取代码的账号密码配置项目配置gitlab仓库配置构建构建构…

CTFer成长之路之Python中的安全问题

Python中的安全问题CTF 1.Python里的SSRF 题目提示 尝试访问到容器内部的 8000 端口和 url path /api/internal/secret 即可获取 flag 访问url&#xff1a; http://f5704bb3-5869-4ecb-9bdc-58b022589224.node3.buuoj.cn/ 回显如下&#xff1a; 通过提示构造payload&…

Android 基础知识4-3.2 EditText(输入框)详解

一、EditText&#xff08;输入框&#xff09;介绍 EditText在开发中也是经常使用的控件&#xff0c;比如&#xff0c;要实现一个登录页面&#xff0c;需要用户输入账号、密码等信息&#xff0c;然后我们或得用户输入的内容&#xff0c;把它交给服务器来判断。因此&#xff0c;这…

远程使用服务器上的Jupyter notebook

记录下如何远程使用服务器上的jupyter notebook。 主要是在服务器端执行以下操作&#xff1a; 激活需要使用的环境使用pip list 或conda list检查是否已经安装notebook。如果没有安装&#xff0c;则使用pip install jupyter notebook进行安装&#xff1b;反之忽略这一步&…

一些硬件学习的注意事项与快捷方法

xilinx系列软件 系统适用版本 要安装在Ubuntu系统的话&#xff0c;要注意提前看好软件适用的版本&#xff0c;不要随便安好了Ubuntu系统又发现对应版本的xilinx软件不支持。 如下图&#xff0c;发行说明中会说明这个版本的软件所适配的系统版本。 下载 vivado vitis这些都可以…

从编年史角度看大数据兴起

开源大数据编年史大数据发展的各阶段大数据诞生初期大数据百花齐放的发展之路追求性能的大数据成熟期大数据发展的各阶段 开源大数据的编年史的话&#xff0c;实际上分为三个阶段。一般来说它分为初期、发展期、成熟期。 初期就是大数据刚开始萌芽的一个阶段&#xff0c;它从…

selenium模块(自动化)

文章目录一、环境配置二、使用selenium解析源码三、基本函数四、子页面&#xff08;ifFrame&#xff09;&#xff08;动作链&#xff0c;拖拽&#xff09;五、实现无可视化界面&#xff0c;规避被检测的风险&#xff08;反反爬&#xff09;六、等待七、异常处理Selenium是自动化…