java线程池拒绝策略使用实践
前言
线程池是开发过程中使用频率较高的一个并发组件之一,本篇会结合踩刀哥之前的实践经验来分享一下线程池拒绝策略的真实使用场景,至于线程池内部原理只会简单介绍,有需要的可以自行上网学习。
线程池工作机制
这里用一个例子来描述下线程池的工作机制,2015年公司boss创立公司,创立初期公司业务比较少,boss一个人(corePoolSize=1)干的有条不紊,没过多久,业务量上来了,他一个人干不过来,分身乏力,那怎么办呢?其实很简单,排队呗,就这样boss将待办的任务都添加到需求池(BlockingQueue)里面,boss又开始愉快的工作,但是客户的耐心终归有限,过了几天发现自己交给我们公司的业务还没完成,客户一气之下打电话给boss“我的活你干完没有,没干的话就停下来(shutdown/shutdownNow)吧,我找别人了”,这时候boss慌了,流着泪点上一根烟,在网上发了招聘,就这样干活的人又多了起来(addWorker),但是员工终归不是无限的,当活太多的时候,boss还是会拒绝接一些活(RejectedExecutionHandler)。公司在boss的带领下沉浮五载,本以为2020年可以大干一场,却偏偏赶上了新冠,复工日期一拖再拖,客户需求一少再少,唯独公司养的员工没少,这是公司目前最大的开支了。长痛不如短痛,boss们研究了一个政策,如果员工一个月(keepAliveTime=一个月)没有活干,那么就会被辞退(空闲线程被清理),一段时间以后不少员工被辞退了,只剩下核心人员。
画个简图帮助理解,如下:
主角登场
之前的铺垫都是为了引出RejectedExecutionHandler,现在我们来聊聊RejectedExecutionHandler的真实使用场景,先看看RejectedExecutionHandler的定义。
/**
* A handler for tasks that cannot be executed by a {@link ThreadPoolExecutor}.
*
* @since 1.5
* @author Doug Lea
*/
public interface RejectedExecutionHandler {
/**
* Method that may be invoked by a {@link ThreadPoolExecutor} when
* {@link ThreadPoolExecutor#execute execute} cannot accept a
* task. This may occur when no more threads or queue slots are
* available because their bounds would be exceeded, or upon
* shutdown of the Executor.
*
* <p>In the absence of other alternatives, the method may throw
* an unchecked {@link RejectedExecutionException}, which will be
* propagated to the caller of {@code execute}.
*
* @param r the runnable task requested to be executed
* @param executor the executor attempting to execute this task
* @throws RejectedExecutionException if there is no remedy
*/
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
很显然它是一个接口,只有一个方法rejectedExecution,当线程池拒绝接受任务的时候会调用它,RejectedExecutionHandler一般由构造ThreadPoolExecutor对象的时候传入,如果没有传入会默认使用AbortPolicy。
jdk目前已提供四种RejectedExecutionHandler的实现供开发者使用,大多数情况下已够用,少数情况下用户可以选择自定义,jdk提供的四种RejectedExecutionHandler实现如下:
1.AbortPolicy:中止策略,抛出RejectedExecutionException异常由使用者处理;
2.CallerRunsPolicy:占用调用者的线程来执行被拒绝的任务;
3.DiscardOldestPolicy:将最早入队列的任务丢弃,然后重新提交被拒绝的任务(这里有可能依然不成功);
4.DiscardPolicy:抛弃策略,简单的抛弃,和AbortPolicy比较相似,区别是前者对于用户无感知;
实践场景之-AbortPolicy
在踩刀哥过往的工作中有这么一个需求,用户支付以后给用户push消息,这里就用到了线程池来处理这块业务,伪代码如下:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(...);
try{
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
//1. 调用推送服务push msg
}
});
}catch (Exception RejectedExecutionException){
//2. 记录日志
}
前面说过,如果构造ThreadPoolExecutor时没有传递RejectedExecutionHandler,jdk默认会使用AbortPolicy,它内部会抛出RejectedExecutionException,所以调用者需要捕获这个异常做相应的处理,因为当时1.0的需求比较简单,所以只是简单了记录了日志,后来产品提出对于这种失败的情况需要做补偿,进而引出下面的第二个使用场景。
实践场景之-自定义RejectedExecutionHandler
前面提到产品希望对于这种被拒绝的push任务需要做补偿,具体的补偿逻辑为:如果当时被拒绝了,那就每隔2s重试一次,一共重试2次。我当时的处理措施是,如果execute失败了那就将任务放到redis中,异步取出重试,代码怎么写呢,第一版是这么写的:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(...);
try{
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
//1. 调用推送服务push msg
}
});
}catch (Exception RejectedExecutionException){
//2. 将任务添加到redis中
}
//3 定时任务扫描redis,然后添加到threadPoolExecutor中
看着确实也没有问题,也能实现功能,但是这种写法显得不太优雅,ThreadPoolExecutor对于拒绝处理这块采用了策略设计模式来优化代码,让逻辑更清晰,而我现在的写法将任务处理和拒绝处理揉在了一起,违背了原来的设计,所以决定进行改造,改造后如下:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( ..., new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable task, ThreadPoolExecutor executor) {
//1. 记录日志
log.warn...
//2 将task插入的redis中
redis.lpush...
}
});
拒绝处理的逻辑被封装到自定义的拒绝处理器当中,逻辑更清晰,表达能力更强。
实践场景之-CallerRunsPolicy
之前做过一个http推送平台,大体工作流程如下:
1.生产者将推送任务插入数据库中;
2.推送平台起一个异步线程去获取待推送任务;
3.将第2步中得到的推送任务丢到线程池里面去推送。
简单来说就是一个生产者消费者模型,推送的时候发现某些下游的接口响应时间较长,经常将线程池占满,所以就希望DelayQueuePollingTask这个线程能感知到这一情况,当线程池满的时候停止去数据库获取待推送任务,所以就将RejectedExecutionHandler设置为CallerRunsPolicy,现在可以达到如下效果:
1.生产者将推送任务插入数据库中;
2.推送平台起一个异步线程去获取待推送任务;
3.将第2步中得到的推送任务丢到线程池里面去推送;
4.线程池如果满就由DelayQueuePollingTask这个Thread自己执行推送任务,这样就可以停止去数据库获取待推送任务,DelayQueuePollingTask也不至于闲着没事,还可以分担任务。
java线程池拒绝策略使用实践的更多相关文章
- Java线程池拒绝策略
Java线程池拒绝策略 相关资料: 线程池的RejectedExecutionHandler(拒绝策略):http://blog.csdn.net/jgteng/article/details/544 ...
- Spring Boot中如何配置线程池拒绝策略,妥善处理好溢出的任务
通过之前三篇关于Spring Boot异步任务实现的博文,我们分别学会了用@Async创建异步任务.为异步任务配置线程池.使用多个线程池隔离不同的异步任务.今天这篇,我们继续对上面的知识进行完善和优化 ...
- JUC线程池之 线程池拒绝策略
拒绝策略介绍 线程池的拒绝策略,是指当任务添加到线程池中被拒绝,而采取的处理措施. 当任务添加到线程池中之所以被拒绝,可能是由于:第一,线程池异常关闭.第二,任务数量超过线程池的最大限制. 线程池共包 ...
- Java 线程池 8 大拒绝策略,面试必问!
前言 谈到java的线程池最熟悉的莫过于ExecutorService接口了,jdk1.5新增的java.util.concurrent包下的这个api,大大的简化了多线程代码的开发.而不论你用Fix ...
- java线程池实践
线程池大家都很熟悉,无论是平时的业务开发还是框架中间件都会用到,大部分都是基于JDK线程池ThreadPoolExecutor做的封装, 都会牵涉到这几个核心参数的设置:核心线程数,等待(任务)队列, ...
- 深入源码分析Java线程池的实现原理
程序的运行,其本质上,是对系统资源(CPU.内存.磁盘.网络等等)的使用.如何高效的使用这些资源是我们编程优化演进的一个方向.今天说的线程池就是一种对CPU利用的优化手段. 通过学习线程池原理,明白所 ...
- Java线程池基础
目录: 一.线程池概述 二.线程池参数 三.线程池的执行过程 四.线程池的主要实现 五.线程池的使用 六.线程池的正确关闭方式 七.线程池参数调优 一.线程池概述 1.线程池类 目前线程池类一般有两个 ...
- Java线程池(ExecutorService)使用
一.前提 /** * 线程运行demo,运行时打出线程id以及传入线程中参数 */ public class ThreadRunner implements Runnable { private fi ...
- Java线程池的拒绝策略
一.简介 jdk1.5 版本新增了JUC并发编程包,极大的简化了传统的多线程开发.前面文章中介绍了线程池的使用,链接地址:https://www.cnblogs.com/eric-fang/p/900 ...
随机推荐
- 微服务实战系列(五)-注册中心Eureka与nacos区别
1. 场景描述 nacos最近用的比较多,介绍下nacos及部署吧,刚看了下以前写过类似的,不过没写如何部署及与eureka区别,只展示了效果,补补吧. 2.解决方案 2.1 nacos与eureka ...
- 大话Python切片,助你彻底掌控切片小妖精
切片语义 生活中切黄瓜.切萝卜.一本书的每一页等等都是符合切片的语义 切片的语义是从某个东西中通过某种手段拿到某个整体的一部分 切片是拿来主义,建立在已经有的序列上,有黄瓜才能切黄瓜 列表 -> ...
- Django-当前菜单激活状态-模版 request | slice
如何满足这个需求? 1. view中传递过来一个当前页面的参数标识,通过模版语言进行判断 {% if current_page == 'index' %}active{% endif %} # 每一个 ...
- c++中的GetModuleFileName函数的用法以及作用
参考: 1. http://blog.sina.com.cn/s/blog_b078a1cb0101fw48.html 2. https://www.cnblogs.com/Satu/p/820393 ...
- CF877E Danil and a Part-time Job
题目大意: link 有一棵 n 个点的树,根结点为 1 号点,每个点的权值都是 1 或 0 共有 m 次操作,操作分为两种 get 询问一个点 x 的子树里有多少个 1 pow 将一个点 x 的子树 ...
- MQTT消息队列压力测试
环境准备: jmeter插件下载:mqttxmeter1.0.1jarwithdependencies.jar 把MQTT插件放在 %JMeter_Home%/lib/ext下.重启jmeter. M ...
- angular http 跨域访问
1.在跟目录中创建配置文件:proxy.config.json ,文件内容如下: { "/api": { "target": "http://192. ...
- FastJson解析Json,封装JavaBean对象
获取到前端的Json,后台对应封装JavaBean对象,对其解析赋值 获取到前端的json,对其进行分析 1.获取最外层前端json对应得JavaBean (1)未分析格式的json串 (2)初步格式 ...
- 多测师讲解python ____字典,字符,元组,集合(转换)___高级讲师肖sir
1.字符转换 a =['a','b','c','d','e'] #定义一个列表b =[1,2,3,4,5] #定义一个列表c=zip(a,b)# zip类:可以将两个列表进行拼接,返回一个列表且列表中 ...
- matplotlib画图教程,设置坐标轴标签和间距
大家好,欢迎来到周四数据处理专题,我们今天继续matplotlib作图教程. 在上周的文章当中我们介绍了如何通过xlabel和ylabel设置坐标轴的名称,以及这两个函数的花式设置方法,可以设置出各种 ...