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 ...
随机推荐
- E-Form++ for Windows CE源码库2020,嵌入式开放源码!
E-Form++ for Windows CE源码库2020! 现在就把这个下载到您的Windows CE中,体验极致HMI触摸. Windows CE评估版下载! 1. E-Form++ for ...
- mysql存储过程的初步学习及案例示例
存储过程 几个月前小编开始初步接触学习存储过程,当然是跟着大神的视频学习的,在学习的过程中自己也记录了一下笔记,如今整理一下,接下来我将从概念,优缺点以及语法和实际应用几方面为大家详细讲解一下存储过程 ...
- VS2010,VS2012,VS2013,VS2015等的自动提示不能默认选中的功能解决办法
很简单,只需要按 ctrl+alt+space 即可切换.
- 烦人的Null,你可以走开点了
1. Null 的问题 假设现在有一个需要三个参数的方法.其中第一个参数是必须的,后两个参数是可有可无的. 第一种情况,在我们调用这个方法的时候,我们只能传入两个参数,对第三个参数,我们在上下文里是没 ...
- C/C++ 中 exit() 函数
参考: https://blog.csdn.net/jjjcainiao/article/details/21935795 知乎上的问题]C/C++ 中 exit() 函数的参数到底有什么意义? C ...
- JDBC Java 程序从 MySQL 数据库中读取数据,并封装到 Javabean 对象中
MySQL 版本:Server version: 5.7.17-log MySQL Community Server (GPL) 相关内容:JDBC Java 连接 MySQL 数据库 用于测试的 M ...
- JavaScript封装函数:获取下一个/上一个兄弟元素节点
要求: 获得下一个/上一个兄弟元素节点,不包括文本节点等 解决IE兼容性问题 代码实现: 获得下一个兄弟元素节点: function getNextElement(element) { var el ...
- 最全153道Spring全家桶面试题,你都碰到过哪些?(含答案解析)
前言 Spring 框架自诞生以来一直备受开发者青睐,有人亲切的称之为:Spring 全家桶. 毋庸置疑,Spring 早已成为 Java 后端开发的行业标准,无数的公司选择 Spring 作为基础的 ...
- Java变量命名前俩个字母仅含有一个大写字母的坑
背景 前几周在做项目fetch切换,即将HttpUtils调用改成使用Feign调用.大概代码如下: // 原代码 String resultJson = HttpUtil.get(url + &qu ...
- C#数据结构-双向链表
链表的概念以及链表与数组的差异不做过多的叙述,相信大家都耳熟能详,这里以c#语言实现简单的双向链表,作为备用,记录下~ public class Node<T> { private Nod ...