CountDownLatch和CyclicBarrier:如何让多线程步调一致?
案例:对账系统的业务是这样的,用户通过在线商城下单,会生成电子订单,保存在订单库;之后物流会生成派送单给用户发货,派送单保存在派送单库。为了防止漏派送或者重复派送,对账系统每天还会校验是否存在异常订单。对账系统的处理逻辑很简单,系统流程图如下。目前对账系统的处理逻辑是首先查询订单,然后查询派送单,之后对比订单和派送单,将差异写入差异库。
对上面的代码抽象就是这样的,就是在一个单线程里面循环查询订单、派送单,然后执行对账,最后将写入差异库。
while(存在未对账订单){
// 查询未对账订单
pos = getPOrders();
// 查询派送单
dos = getDOrders();
// 执行对账操作
diff = check(pos, dos);
// 差异写入差异库
save(diff);
}
1)上面的系统现在执行很慢,该怎样优化来执行速度呢?
目前是单线程的,那单线程的话我们就考虑是否可以用多线程来做。查询未对账订单和查询派送单这两个操作是可以并行处理的。
2)实现查询对账订单和查询派送单并行执行的代码应该是怎样的?
while(存在未对账订单){
// 查询未对账订单
Thread T1 = new Thread(()->{
pos = getPOrders();
});
T1.start();
// 查询派送单
Thread T2 = new Thread(()->{
dos = getDOrders();
});
T2.start();
// 等待T1、T2结束
T1.join();
T2.join();
// 执行对账操作
diff = check(pos, dos);
// 差异写入差异库
save(diff);
}
我们在主线程中开了两个插队的线程,等这两个查询的插队线程执行完了,阻塞的主线程被唤醒,那么就可以执行对账还有写入差异库的操作了。
3)思考一下,我们上面的代码还有没有优化的空间呢?
我们每次进行新的查询的对账的时候,都要创建两个新的线程出来,我们知道创建线程是比较好费时间的。那思考一下可不可以用线程池来减少创建线程的开销呢。
// 创建2个线程的线程池
Executor executor =
Executors.newFixedThreadPool(2);
while(存在未对账订单){
// 查询未对账订单
executor.execute(()-> {
pos = getPOrders();
});
// 查询派送单
executor.execute(()-> {
dos = getDOrders();
});
/* ??如何实现等待??*/
// 执行对账操作
diff = check(pos, dos);
// 差异写入差异库
save(diff);
}
4)使用上面线程池的代码的话,我的join就不能调用了,那我的主线程就不知道什么时候两个查询操作执行完了,这个时候该怎么办?
我们可以使用一个计数器,初始值呢设置为2,查询一次就减1,当两个查询执行完,那计数器就是0了,我们的主线程也就能被唤醒执行了。
5)上面的方案是我们自己想出来的,那java其实提供了一个非常方便的实现我们上面方案的工具类CountDownLatch,那使用CountDownLatch怎样优化我们的代码呢?
主线程的话我们调用await方法来阻塞,两个查询线程我们执行countDown方法,会减1。主线程当检测到为0时就可以执行了
// 创建2个线程的线程池
Executor executor =
Executors.newFixedThreadPool(2);
while(存在未对账订单){
// 计数器初始化为2
CountDownLatch latch =
new CountDownLatch(2);
// 查询未对账订单
executor.execute(()-> {
pos = getPOrders();
latch.countDown();
});
// 查询派送单
executor.execute(()-> {
dos = getDOrders();
latch.countDown();
});
// 等待两个查询操作结束
latch.await();
// 执行对账操作
diff = check(pos, dos);
// 差异写入差异库
save(diff);
}
6)上面使用CountDownLatch和线程池的方案已经很不错了,在思考一下,我们的这个程序还能不能优化一下?
我的对账操作和下一次查询操作其实是不影响的,那么他们之间是可以并发执行的。也就是我在进行本次对账的同时,是可以执行下一次的查询操作的。
7)对账需要查询出数据来才可以执行,这种的话对应什么模型?
生产者-消费者模型
8)既然看出来了是生产者消费者模型,那就需要一个队列,生产者生产出来东西放到队列,消费者去队列当中取。但是针对我们上面的案例,一个队列的话肯会造成数据混乱,我们应该怎样设计?
使用两个队列,两个队列间的元素还有对应关系。订单查询操作将订单查询结果插入订单队列,派送单查询操作将派送单插入派送单队列,这两个队列的元素之间是有一一对应的关系的。我们的对账操作每次从两个队列当中各取一个,这样数据肯定不会发生混乱。
9)我们如何用代码来实现查询和对账之间的并行呢?
使用三个线程,一个线程 T1 执行订单的查询工作,一个线程 T2 执行派送单的查询工作,当线程 T1 和 T2 都各自生产完 1 条数据的时候,通知线程 T3 执行对账操作。这个想法虽看上去简单,但其实还隐藏着一个条件,那就是线程 T1 和线程 T2 的工作要步调一致,不能一个跑得太快,一个跑得太慢,只有这样才能做到各自生产完 1 条数据的时候,通知线程 T3。
10)我们上面的方案有哪些要解决的问题?怎样解决?
T1和T2要走的齐
他们执行完之后要能通知到T3
解决这两个问题的方案也很简单,还是搞一个计数器,初始化为2,T1执行完减1,T2执行完减1。当计数器值为0时,T3就可以执行了,T3执行的时候把我们计数器又重置为2,此时T1,T2又可以执行了。
11)实际项目中java其实给了我们现成的实现上面方案的工具类CyclicBarrier,代码实现的怎样的?
CyclicBarrier 的计数器有自动重置的功能,当减到 0 的时候,会自动重置你设置的初始值,所以他要带个循环
// 订单队列
Vector<P> pos;
// 派送单队列
Vector<D> dos;
// 执行回调的线程池
Executor executor =
Executors.newFixedThreadPool(1);
final CyclicBarrier barrier =
new CyclicBarrier(2, ()->{
executor.execute(()->check());
});
void check(){
P p = pos.remove(0);
D d = dos.remove(0);
// 执行对账操作
diff = check(p, d);
// 差异写入差异库
save(diff);
}
void checkAll(){
// 循环查询订单库
Thread T1 = new Thread(()->{
while(存在未对账订单){
// 查询订单库
pos.add(getPOrders());
// 等待
barrier.await();
}
});
T1.start();
// 循环查询运单库
Thread T2 = new Thread(()->{
while(存在未对账订单){
// 查询运单库
dos.add(getDOrders());
// 等待
barrier.await();
}
});
T2.start();
}
CountDownLatch和CyclicBarrier:如何让多线程步调一致?的更多相关文章
- 多线程进阶---Thread.join()/CountDownLatch.await() /CyclicBarrier.await()
Thread.join() CountDownLatch.await() CyclicBarrier.await() 三者都是用来控制程序的"流动" 可以让程序"堵塞&q ...
- Java多线程:CountDownLatch、CyclicBarrier 和 Semaphore
场景描述: 多线程设计过程中,经常会遇到需要等待其它线程结束以后再做其他事情的情况. 有几种方案: 1.在主线程中设置一自定义全局计数标志,在工作线程完成时,计数减1.主线程侦测该标志是否为0,一 ...
- java多线程并发控制countDownLatch和cyclicBarrier的使用
java主线程等待所有子线程执行完毕在执行,这个需求其实我们在工作中经常会用到,比如用户下单一个产品,后台会做一系列的处理,为了提高效率,每个处理都可以用一个线程来执行,所有处理完成了之后才会返回给用 ...
- 多线程学习笔记六之并发工具类CountDownLatch和CyclicBarrier
目录 简介 CountDownLatch 示例 实现分析 CountDownLatch与Thread.join() CyclicBarrier 实现分析 CountDownLatch和CyclicBa ...
- Java多线程-CountDownLatch、CyclicBarrier、Semaphore
上次简单了解了多线程中锁的类型,今天要简单了解下多线程并发控制的一些工具类了. 1. 概念说明: CountDownLatch:相当于一个待执行线程计数器,当计数减为零时表示所有待执行线程都已执行完毕 ...
- java多线程10:并发工具类CountDownLatch、CyclicBarrier和Semaphore
在JDK的并发包(java.util.concurrent下)中给开发者提供了几个非常有用的并发工具类,让用户不需要再去关心如何在并发场景下写出同时兼顾线程安全性与高效率的代码. 本文分别介绍Coun ...
- 【Java多线程】JUC包下的工具类CountDownLatch、CyclicBarrier和Semaphore
前言 JUC中为了满足在并发编程中不同的需求,提供了几个工具类供我们使用,分别是CountDownLatch.CyclicBarrier和Semaphore,其原理都是使用了AQS来实现,下面分别进行 ...
- 多线程工具类:CountDownLatch、CyclicBarrier、Semaphore、LockSupport
◆CountDownLatch◆ 假如有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以.比如你想要买套房子,但是呢你现在手上没有钱.你得等这个月工资发了.然后年终奖发了.然后朋友借你得钱 ...
- 多线程-CountDownLatch,CyclicBarrier,Semaphore,Exchanger,Phaser
CountDownLatch 一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待.用给定的计数初始化CountDownLatch.调用countDown()计数减一, ...
- 温故知新-多线程-forkjoin、CountDownLatch、CyclicBarrier、Semaphore用法
Posted by 微博@Yangsc_o 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0 文章目录 摘要 forkjoin C ...
随机推荐
- HMS Core挑战赛故事:鞋、街景、手办、玩具,原来这些都可以3D建模
HMS Core线上Codelabs挑战赛第3期中,开发者通过学习和运用HMS Core开发的3D建模服务,生成3D建模应用demo,再使用demo为自己身边的一个实物完成建模.在提交的作品中,小编发 ...
- Java课程设计---WindowBuilder插件安装
1 .获取插件地址 WindowBuilder 地址http://www.eclipse.org/windowbuilder/download.php 打开网址后会看到如下 2.在线方式安装插件 根据 ...
- Python:wordcloud
wordcloud官方文档 1.简介 wordcloud是优秀的词云展示的第三方库 2.导入模块 import wordcloud 3.wordcloud对象初始化 以下参数值均为官方文档给出的默认值 ...
- 如何建立自己的代理IP池,减少爬虫被封的几率
如何建立自己的代理IP池,减少爬虫被封的几率 在爬虫过程中,难免会遇到各种各样的反爬虫,运气不好,还会被对方网站给封了自己的IP,就访问不了对方的网站,爬虫也就凉凉. 代理参数-proxies 首先我 ...
- HarmonyOS UI组件在线预览,程序员直呼“不要太方便~”
一.介绍 以往大家如果想查看组件的使用效果,需要打开DevEco Studio构建工程.现在为了便于大家高效开发,文档上线了JS UI组件在线预览功能,无需本地构建工程,在线即可修改组件样式等参数.一 ...
- Maven安装与配置——详细教程
一.安装Maven 进入Maven官网,下载安装包(https://maven.apache.org/download.cgi) . 2.下载完成后,解压到某一路径下.本文以C:\Soft\Java\ ...
- Python:pyglet学习(3):游戏循环
在我们编游戏时,经常会用到一个无限循环,这就叫游戏循环. 先用@win.event试试 import pyglet as p win=p.window.Window(800,600) @win.eve ...
- 常用的公共 DNS 服务器 IP 地址
转载自:小哈龙 2019-04-12 09:34:42 公共 DNS 服务器 IP 地址 名称 DNS 服务器 IP 地址 阿里 AliDNS 223.5.5.5 223.6.6.6 CNNIC SD ...
- Spring MVC 04-- 接收前端参数json格式的方式
/** * 第一种:以RequestParam接收 * 前端传来的是json数据不多时:{"id":1},可以直接用@RequestParam来获取值 * * @param id ...
- 6月6日 python复习 面向对象
1.面向对象编程 1.面向过程编程核心是"过程"二字,过程指的是解决问题的步骤,即先干什么再干什么基于该思想编写程序就好比在编写一条流水线,是一种机械式的思维方式 优点:复杂的问题 ...