【转】通过CountDownLatch提升请求处理速度
countdownlatch是java多线程包concurrent里的一个常见工具类,通过使用它可以借助线程能力极大提升处理响应速度,且实现方式非常优雅。今天我们用一个实际案例和大家来讲解一下如何使用以及需要特别注意的点。
由于线程类的东西都比较抽象,我们换一种讲解思路,先讲解决问题的案例,然后再解释下原理。
假设在微服务架构中,A服务会调用B服务处理一些事情,且每处理一次业务,A可能要调用B多次处理逻辑相同但数据不同的事情。为了提升整个链路的处理速度,我们自然会想到是否可以把A调用B的各个请求组成一个批次,这样A服务只需要调用B服务一次,等B服务处理完一起返回即可,省了多次网络传输的时间。代码如下:
/**
* 批次请求处理服务
* @param batchRequests 批次请求对象列表
* @return
*/
public List<DealResult> deal(List<DealRequest> batchRequests){
List<DealResult> resultList = new ArrayList<>();
if(batchRequests != null){
for(DealRequest request : batchRequests){
//遍历顺序处理单个请求
resultList.add(process(request));
}
}
return resultList;
}
但是B服务顺序处理批次里每一个请求的时间并没有节省,假设批次里有3个请求,一个请求平均耗时100MS,则B服务还是要花费300MS来处理完。有什么办法能立刻简单提升3倍处理速度,令总花费时间只需要100MS?到我们的大将countdownlatch出场了!代码如下:
/**
* 使用countdownlatch的批次请求处理服务
* @param batchRequests 批次请求对象列表
* @return
*/
public List<DealResult> countDownDeal(List<DealRequest> batchRequests){
//定义线程安全的处理结果列表
List<DealResult> countDownResultList = Collections.synchronizedList(new ArrayList<DealResult>());
if(batchRequests != null){
//定义countdownlatch线程数,有多少个请求,我们就定义多少个
CountDownLatch runningThreadNum = new CountDownLatch(batchRequests.size());
for(DealRequest request : batchRequests){
//循环遍历请求,并实例化线程(构造函数传入CountDownLatch类型的runningThreadNum),立刻启动
DealWorker dealWorker = new DealWorker(request, runningThreadNum, countDownResultList);
new Thread(dealWorker).start();
}
try {
//调用CountDownLatch的await方法则当前主线程会等待,直到CountDownLatch类型的runningThreadNum清0
//每个DealWorker处理完成会对runningThreadNum减1
//如果等待1分钟后当前主线程都等不到runningThreadNum清0,则认为超时,直接中断,抛出中断异常InterruptedException
runningThreadNum.await(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
//此处简化处理,非正常中断应该抛出异常或返回错误结果
return null;
}
}
return countDownResultList;
}
/**
* 线程请求处理类
*
*/
private class DealWorker implements Runnable {
/** 正在运行的线程数 */
private CountDownLatch runningThreadNum;
/**待处理请求*/
private DealRequest request;
/**待返回结果列表*/
private List<DealResult> countDownResultList;
/**
* 构造函数
* @param request 待处理请求
* @param runningThreadNum 正在运行的线程数
* @param countDownResultList 待返回结果列表
*/
private DealWorker(DealRequest request, CountDownLatch runningThreadNum, List<DealResult> countDownResultList) {
this.request = request;
this.runningThreadNum = runningThreadNum;
this.countDownResultList = countDownResultList;
}
@Override
public void run() {
try{
this.countDownResultList.add(process(this.request));
}finally{
//当前线程处理完成,runningThreadNum线程数减1,此操作必须在finally中完成,避免处理异常后造成runningThreadNum线程数无法清0
this.runningThreadNum.countDown();
}
}
}
是不是很简单?下图和上面的代码又做了一个对应,假设有3个请求,则启动3个子线程DealWorker,并实例化值数等于3的CountDownLatch。每当一个子线程处理完成后,则调用countDown操作减1。主线程处于awaiting状态,直到CountDownLatch的值数减到0,则主线程继续resume执行。
在API中是这样描述的:
用给定的计数 初始化 CountDownLatch。由于调用了
countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await
的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。
经典的java并发编程实战一书中做了更深入的定义:CountDownLatch属于闭锁的范畴,闭锁是一种同步工具类,可以延迟线程的进度直到其到达终止状态。闭锁的作用相当于一扇门:在闭锁到达结束状态之前(上面代码中的runningThreadNumq清0),这扇门一直是关闭的,并且没有任何线程能通过(上面代码中的主线程一直await),当到达结束状态时,这扇门会打开并允许所有线程通过(上面代码中的主线程可以继续执行)。当闭锁到达结束状态后,将不会再改变状态,因此这扇门将永远保持打开状态。
像FutureTask,Semaphore这类在concurrent包里的类也属于闭锁,不过它们和CountDownLatch的应用场景还是有差别的,这个我们在后面的文章里再细说。
使用CountDownLatch有哪些需要注意的点
- 批次请求之间不能有执行顺序要求,否则多个线程并发处理无法保证请求执行顺序
- 各线程都要操作的结果列表必须是线程安全的,比如上面代码范例的countDownResultList
- 各子线程的countDown操作要在finally中执行,确保一定可以执行
- 主线程的await操作需要设置超时时间,避免因子线程处理异常而长时间一直等待,如果中断需要抛出异常或返回错误结果
使用CountDownLatch提高批次处理速度的问题
- 如果一个批次请求数很多,会瞬间占用服务器大量线程。此时必须使用线程池,并限定最大可处理线程数量,否则服务器不稳定性会大福提升。
- 主线程和子线程间的数据传输变得困难,稍不注意会造成线程不安全的问题,且代码可读性有一定下降
地址:https://segmentfault.com/a/1190000011443338
【转】通过CountDownLatch提升请求处理速度的更多相关文章
- 用CountDownLatch提升请求处理速度
countdownlatch是java多线程包concurrent里的一个常见工具类,通过使用它可以借助线程能力极大提升处理响应速度,且实现方式非常优雅.今天我们用一个实际案例和大家来讲解一下如何使用 ...
- 详解服务器性能测试的全生命周期?——从测试、结果分析到优化策略(转载)
服务器性能测试是一项非常重要而且必要的工作,本文是作者Micheal在对服务器进行性能测试的过程中不断摸索出来的一些实用策略,通过定位问题,分析原因以及解决问题,实现对服务器进行更有针对性的优化,提升 ...
- Web服务端性能提升实践
随着互联网的不断发展,日常生活中越来越多的需求通过网络来实现,从衣食住行到金融教育,从口袋到身份,人们无时无刻不依赖着网络,而且越来越多的人通过网络来完成自己的需求. 作为直接面对来自客户请求的Web ...
- 【性能压测】:MQ队列异步处理机制导致的系统无法接受请求的问题
一,最近压测系统交易峰值时,因该支交易采用MQ异步队列处理机制:该增加积分的交易,前段服务器优先返回给客户增加积分成功的结果,后端的MQ队列服务器再慢慢处理该请求: 二,压测过程中出现的问题现象:前几 ...
- JAVA RPC (七) 手把手从零教你写一个生产级RPC之client请求
上节说了关于通用请求代理,实际上对spring的bean引用都是通过koalasClientProxy来实现的,那么在代理方法中才是我们实际的发送逻辑,咱们先看一下原生的thrift请求是什么样的. ...
- Nginx 限制并发连接和并发请求数配置
Nginx限制并发连接和并发请求数配置 by:授客 QQ:1033553122 测试环境 nginx-1.10.0 配置介绍 查看是否内置模块 # pwd /mnt/nginx-1.10.0 ...
- Web 请求之--性能相关
本博客代码运行环境 python : Python 3.7.1rc1 version pip : pip 19.1.1 version Scrapy: scrapy 1.6.0 version asy ...
- Java 面试知识点【背诵版 240题 约7w字】
-- 转载自牛客网 是瑶瑶公主吖 Java 基础 40 语言特性 12 Q1:Java 语言的优点? ① 平台无关性,摆脱硬件束缚,"一次编写,到处运行". ② 相对安全的内存管理 ...
- 详解MySQL大表优化方案( 转)
当MySQL单表记录数过大时,增删改查性能都会急剧下降,可以参考以下步骤来优化: 单表优化 除非单表数据未来会一直不断上涨,否则不要一开始就考虑拆分,拆分会带来逻辑.部署.运维的各种复杂度,一般以整型 ...
随机推荐
- Svn和jekins的使用
首先是svn的安装, 在安装svn的服务端时,需要注意选择第二项,这样才能在idea中使用svn, 然后就是在idaa中配置svn插件. 插件的使用 从svn库中检出项目 然后在这个地址中填入你需要导 ...
- 配置国内的maven仓库
MAVEN中央仓库 国内 配置Maven中央仓库路径的方法如下: 在Maven文件的conf目录中打开settings.xml文件 在文件中的servers节点和mirror节点中加入如下内容 华为云 ...
- flask组件之flask_script和flask_migrate的使用
flask_script的使用 作用: 使用命令启动flask项目 # 在项目的启动文件中 from flask import Flask from settings import Settings ...
- JavaScript基础事件(6)
day53 参考:https://www.cnblogs.com/liwenzhou/p/8011504.html#autoid-2-3-8 事件 HTML 4.0 的新特性之一是有能力使 HTML ...
- php错误控制运算符@
PHP支持一个错误控制运算符:@.当将其放置在一个PHP表达式之前,该表达式可能产生的任何错误信息都被忽略掉. 如果用set_error_handle()设定了自定义的错误处理函数,仍然会被调用,但是 ...
- [Angular 6] 初学angular,环境全部最新,[ ng serve ] 不能启动,卡在 95% 不动 => 解决方案
2018.9.7 问题描述: 通过ng serve命令启动angular应用时,卡在95%, ctrl+c 停掉后看到错误内容为找不到ng_modules下的angular模块下的package.js ...
- nacicat premium 快捷键
1.ctrl+q 打开查询窗口 2.ctrl+/ 注释sql语句 3.ctrl+shift +/ 解除注释 4.ctrl+r 运行查询窗口的s ...
- 【bzoj2422】 Times 前缀和
本来想练一下树状数组的,看到网上某人的blog后点了进来. 第一眼发现不会,出去上了个厕所发现离散化后不是一道简单前缀和题吗. 考虑到每一个人出现且仅出现一次,且出现的时间是在一个连续的区间内. 那么 ...
- LruCache源码分析
LRU(Least Recently Used)是一种很常用的资源调度策略,与20/80原则契合,在资源达到上限时倾向保留最近经常访问的资源对象. Android中基于LRU实现了缓存对象,即LruC ...
- (转)PLSQL Developer 12.0.7连接Oracle12c数据库
版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/sl1992/article/details/80489413 1.下载安装PL/SQL Develo ...