【并发编程】CountDownLatch

📝个人主页:五敷有你
🔥系列专栏:并发编程
⛺️稳中求进,晒太阳

CountDownLatch 概念
CountDownLatch可以使一个获多个线程等待其他线程各自执行完毕后再执行。
CountDownLatch 定义了一个计数器,和一个阻塞队列, 当计数器的值递减为0之前,阻塞队列里面的线程处于挂起状态,当计数器递减到0时会唤醒阻塞队列所有线程,这里的计数器是一个标志,可以表示一个任务一个线程,也可以表示一个倒计时器,CountDownLatch可以解决那些一个或者多个线程在执行之前必须依赖于某些必要的前提业务先执行的场景。
CountDownLatch 常用方法说明
CountDownLatch(int count); //构造方法,创建一个值为count 的计数器。 await();//阻塞当前线程,将当前线程加入阻塞队列。 await(long timeout, TimeUnit unit);//在timeout的时间之内阻塞当前线程,时间一过则当前线程可以执行, countDown();//对计数器进行递减1操作,当计数器递减至0时,当前线程会去唤醒阻塞队列里的所有线程。
用CountDownLatch 来优化我们的报表统计
功能现状
运营系统有统计报表、业务为统计每日的用户新增数量、订单数量、商品的总销量、总销售额......等多项指标统一展示出来,因为数据量比较大,统计指标涉及到的业务范围也比较多,所以这个统计报表的页面一直加载很慢,所以需要对统计报表这块性能需进行优化。
问题分析
统计报表页面涉及到的统计指标数据比较多,每个指标需要单独的去查询统计数据库数据,单个指标只要几秒钟,但是页面的指标有10多个,所以整体下来页面渲染需要将近一分钟。
解决方案
任务时间长是因为统计指标多,而且指标是串行的方式去进行统计的,我们只需要考虑把这些指标从串行化的执行方式改成并行的执行方式,那么整个页面的时间的渲染时间就会大大的缩短, 如何让多个线程同步的执行任务,我们这里考虑使用多线程,每个查询任务单独创建一个线程去执行,这样每个统计指标就可以并行的处理了。
要求
因为主线程需要每个线程的统计结果进行聚合,然后返回给前端渲染,所以这里需要提供一种机制让主线程等所有的子线程都执行完之后再对每个线程统计的指标进行聚合。 这里我们使用CountDownLatch 来完成此功能。
模拟代码
1、分别统计4个指标用户新增数量、订单数量、商品的总销量、总销售额;
2、假设每个指标执行时间为3秒。如果是串行化的统计方式那么总执行时间会为12秒。
3、我们这里使用多线程并行,开启4个子线程分别进行统计
4、主线程等待4个子线程都执行完毕之后,返回结果给前端。
//用于聚合所有的统计指标private static Map map=new HashMap();//创建计数器,这里需要统计4个指标private static CountDownLatch countDownLatch=new CountDownLatch(4);
public static void main(String[] args) {
//记录开始时间long startTime=System.currentTimeMillis();
Thread countUserThread=new Thread(new Runnable() {public void run() {try {System.out.println("正在统计新增用户数量");Thread.sleep(3000);//任务执行需要3秒map.put("userNumber",1);//保存结果值countDownLatch.countDown();//标记已经完成一个任务System.out.println("统计新增用户数量完毕");} catch (InterruptedException e) {e.printStackTrace();}
}});Thread countOrderThread=new Thread(new Runnable() {public void run() {try {System.out.println("正在统计订单数量");Thread.sleep(3000);//任务执行需要3秒map.put("countOrder",2);//保存结果值countDownLatch.countDown();//标记已经完成一个任务System.out.println("统计订单数量完毕");} catch (InterruptedException e) {e.printStackTrace();}
}});
Thread countGoodsThread=new Thread(new Runnable() {public void run() {try {System.out.println("正在商品销量");Thread.sleep(3000);//任务执行需要3秒map.put("countGoods",3);//保存结果值countDownLatch.countDown();//标记已经完成一个任务System.out.println("统计商品销量完毕");} catch (InterruptedException e) {e.printStackTrace();}
}});
Thread countmoneyThread=new Thread(new Runnable() {public void run() {try {System.out.println("正在总销售额");Thread.sleep(3000);//任务执行需要3秒map.put("countmoney",4);//保存结果值countDownLatch.countDown();//标记已经完成一个任务System.out.println("统计销售额完毕");} catch (InterruptedException e) {e.printStackTrace();}
}});//启动子线程执行任务countUserThread.start();countGoodsThread.start();countOrderThread.start();countmoneyThread.start();
try {//主线程等待所有统计指标执行完毕countDownLatch.await();long endTime=System.currentTimeMillis();//记录结束时间System.out.println("------统计指标全部完成--------");System.out.println("统计结果为:"+map.toString());System.out.println("任务总执行时间为"+(endTime-startTime)/1000+"秒");
} catch (InterruptedException e) {e.printStackTrace();}
}
执行结果

CountDownLatch实现原理
1、创建计数器
当我们调用CountDownLatch countDownLatch=new CountDownLatch(4) 时候,此时会创建一个AQS的同步队列,并把创建CountDownLatch 传进来的计数器赋值给AQS队列的 state,所以state的值也代表CountDownLatch所剩余的计数次数;
public CountDownLatch(int count) {if (count < 0) throw new IllegalArgumentException("count < 0");this.sync = new Sync(count);//创建同步队列,并设置初始计数器值}
2、阻塞线程
当我们调用countDownLatch.wait()的时候,会创建一个节点,加入到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);}
构建阻塞队列的双向链表,挂起当前线程
private void doAcquireSharedInterruptibly(int arg)throws InterruptedException {//新建节点加入阻塞队列final Node node = addWaiter(Node.SHARED);boolean failed = true;try {for (;;) {//获得当前节点pre节点final Node p = node.predecessor();if (p == head) {int r = tryAcquireShared(arg);//返回锁的stateif (r >= 0) {setHeadAndPropagate(node, r);p.next = null; // help GCfailed = false;return;}}//重组双向链表,清空无效节点,挂起当前线程if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}}
3、计数器递减
当我们调用countDownLatch.down()方法的时候,会对计数器进行减1操作,AQS内部是通过释放锁的方式,对state进行减1操作,当state=0的时候证明计数器已经递减完毕,此时会将AQS阻塞队列里的节点线程全部唤醒。
相关文章:
【并发编程】CountDownLatch
📝个人主页:五敷有你 🔥系列专栏:并发编程 ⛺️稳中求进,晒太阳 CountDownLatch 概念 CountDownLatch可以使一个获多个线程等待其他线程各自执行完毕后再执行。 CountDownLatch 定义了一个计数器,…...
2024-HW --->SSRF
这不是马上准备就要护网了嘛,如火如荼的报名ing!!!那么小编就来查缺补漏一下以前的web漏洞,也顺便去收录一波poc!!!! 今天讲的主人公呢就是SSRF,以前学的时候…...
该主机与 Cloudera Manager Server 失去联系的时间过长。 该主机未与 Host Monitor 建立联系
该主机与 Cloudera Manager Server 失去联系的时间过长。 该主机未与 Host Monitor 建立联系 这个去集群主机cm界面上看会出现这个错误 排查思路: 一般比较常见的原因可能是出问题的主机和集群主节点的时间对应不上了。还有就是cm agent服务出现问题了 去该主机的…...
【BUG】No module named ‘dnf‘
报错内容: 类型一 # git clone https://github.com/pytorch/vision.git Cloning into vision... /usr/libexec/git-core/git-remote-https: symbol lookup error: /usr/lib64/libldap.so.2: undefined symbol: EVP_md2, version OPENSSL_1_1_0类型二 # yum reins…...
Ubuntu pycharm配置Conda环境
参考博客:https://blog.csdn.net/qq_40726937/article/details/105323965 https://juejin.cn/post/7229543139950051388 Ubuntu20.04中搭建虚拟环境并且用pycharm调用Ubuntu中的虚拟环境。_ubuntu pycharm的虚拟环境选哪个-CSDN博客...
工作体验记录
文章目录 如何提高说话能力?如何提高行动力?如何完成一个任务产出成果?如何寻找突破点提高解决问题的效率?如何成为技术领导?参考资料 如何提高说话能力? 三思而后说,想清楚问题描述,抓住重点…...
YOLO火灾烟雾检测数据集:20000多张,yolo标注完整
YOLO火灾烟雾检测数据集:一共20859张图像,yolo标注完整,部分图像应用增强 适用于CV项目,毕设,科研,实验等 需要此数据集或其他任何数据集请私信...
基于Spring Boot的餐厅点餐系统
基于Spring Boot的餐厅点餐系统 开发语言:Java框架:springbootJDK版本:JDK1.8数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:Maven3.3.9 部分系统展示 管理员登录界面 用户注册登录界面 …...
tkinter控件教程使用说明(三)
这篇tkinter控件使用教程是最后一 一、TreeView 属性/事件描述代码实例columns列名,用于设置树形视图的列tree["columns"] ("姓名", "年龄", "性别")column列的属性,包括列名、宽度等tree.column("姓名…...
Electron 打包自定义NSIS脚本为安装向导增加自定义页面增加输入框
Electron 打包工具有很多,如Electron-build、 Electron Forge 等,这里使用Electron-build,而Electron-build使用了nsis组件来创建安装向导,默认情况nsis安装向导不能自定义安装向导界面,但是nsis提供了nsis脚本可以扩展…...
Idea2023创建Servlet项目
① Java EE 只是一个抽象的规范,具体实现称为应用服务器。 ② Java EE 只需要两个包 jsp-api.jar 和 servlet-api.jar,而这两个包是没有官方版本的。也就是说,Java 没有提供这两个包,只提供了一个规范。那么这两个包是谁提供的…...
Day57:WEB攻防-SSRF服务端请求Gopher伪协议无回显利用黑白盒挖掘业务功能点
目录 SSRF-原理&挖掘&利用&修复 SSRF无回显解决办法 SSRF漏洞挖掘 SSRF协议利用 http:// (常用) file:/// (常用) dict:// (常用) sftp:// ldap:// tftp:// gopher:// (…...
【Qt】使用Qt实现Web服务器(十):前端基础
1、简述 本人对HTML元素不熟悉,利用QtWebApp加载静态页面来熟悉下HTML元素。 2、测试代码 # a)main中创建 HttpListener new HttpListener(listenerSettings,new RequestMapper(&app),&app);#...
使用vuepress搭建个人的博客(一):基础构建
前言 vuepress是一个构建静态资源网站的库 地址:VuePress 一般来说,这个框架非常适合构建个人技术博客,你只需要把自己写好的markdown文档准备好,完成对应的配置就可以了 搭建 初始化和引入 创建文件夹press-blog npm初始化 npm init 引入包 npm install -D vuepress…...
ArcGIS Pro导出布局时去除在线地图水印
目录 一、背景 二、解决方法 一、背景 在ArcGIS Pro中经常会用到软件自带的在线地图,但是在导出布局时,图片右下方会自带地图的水印 二、解决方法 解决方法:添加动态文本--服务图层制作者名单,然后在布局中选定位置添加 在状…...
启动mysql
删除C:\Program Files (x86)\MySQL\MySQL Server 5.7这个路径下的data文件夹,这个很难删除,因为一开机,mysql的某些服务就启动了,每次重新启动mysql之前,都要删除这个文件夹 因为这个文件夹在后端执行一些我们看不到的…...
C++实现二叉搜索树的增删查改(非递归玩法)
文章目录 一、二叉搜索树的概念结构和时间复杂度二、二叉搜索树的插入三、二叉搜索树的查找四、二叉搜索树的删除(最麻烦,情况最多,一一分析)3.1首先我们按照一般情况下写,不考虑特殊情况下4.1.1左为空的情况ÿ…...
软件架构复用
1.软件架构复用的定义及分类 软件产品线是指一组软件密集型系统,它们共享一个公共的、可管理的特性集,满足某个特定市场或任务的具体需要,是以规定的方式用公共的核心资产集成开发出来的。即围绕核心资产库进行管理、复用、集成新的系统。核心…...
【初阶数据结构】——leetcode:160. 相交链表
文章目录 1. 题目介绍2. 思路1:暴力求解算法思想代码实现 3. 思路2:快慢指针算法思想代码实现 1. 题目介绍 链接: link 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&…...
【Go】goroutine并发常见的变量覆盖案例
越过山丘 遇见六十岁的我 拄着一根白手杖 在听鸟儿歌唱 我问他幸福与否 他笑着摆了摆手 在他身边围绕着一群 当年流放归来的朋友 他说你不必挽留 爱是一个人的等候 等到房顶开出了花 这里就是天下 总有人幸福白头 总有人哭着分手 无论相遇还是不相遇 都是献给岁月的序曲 …...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...
深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法
深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...
CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...
springboot 日志类切面,接口成功记录日志,失败不记录
springboot 日志类切面,接口成功记录日志,失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...
redis和redission的区别
Redis 和 Redisson 是两个密切相关但又本质不同的技术,它们扮演着完全不同的角色: Redis: 内存数据库/数据结构存储 本质: 它是一个开源的、高性能的、基于内存的 键值存储数据库。它也可以将数据持久化到磁盘。 核心功能: 提供丰…...
