前言

线程池是开发过程中使用频率较高的一个并发组件之一,本篇会结合踩刀哥之前的实践经验来分享一下线程池拒绝策略的真实使用场景,至于线程池内部原理只会简单介绍,有需要的可以自行上网学习。

线程池工作机制

这里用一个例子来描述下线程池的工作机制,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线程池拒绝策略使用实践的更多相关文章

  1. Java线程池拒绝策略

    Java线程池拒绝策略 相关资料: 线程池的RejectedExecutionHandler(拒绝策略):http://blog.csdn.net/jgteng/article/details/544 ...

  2. Spring Boot中如何配置线程池拒绝策略,妥善处理好溢出的任务

    通过之前三篇关于Spring Boot异步任务实现的博文,我们分别学会了用@Async创建异步任务.为异步任务配置线程池.使用多个线程池隔离不同的异步任务.今天这篇,我们继续对上面的知识进行完善和优化 ...

  3. JUC线程池之 线程池拒绝策略

    拒绝策略介绍 线程池的拒绝策略,是指当任务添加到线程池中被拒绝,而采取的处理措施. 当任务添加到线程池中之所以被拒绝,可能是由于:第一,线程池异常关闭.第二,任务数量超过线程池的最大限制. 线程池共包 ...

  4. Java 线程池 8 大拒绝策略,面试必问!

    前言 谈到java的线程池最熟悉的莫过于ExecutorService接口了,jdk1.5新增的java.util.concurrent包下的这个api,大大的简化了多线程代码的开发.而不论你用Fix ...

  5. java线程池实践

    线程池大家都很熟悉,无论是平时的业务开发还是框架中间件都会用到,大部分都是基于JDK线程池ThreadPoolExecutor做的封装, 都会牵涉到这几个核心参数的设置:核心线程数,等待(任务)队列, ...

  6. 深入源码分析Java线程池的实现原理

    程序的运行,其本质上,是对系统资源(CPU.内存.磁盘.网络等等)的使用.如何高效的使用这些资源是我们编程优化演进的一个方向.今天说的线程池就是一种对CPU利用的优化手段. 通过学习线程池原理,明白所 ...

  7. Java线程池基础

    目录: 一.线程池概述 二.线程池参数 三.线程池的执行过程 四.线程池的主要实现 五.线程池的使用 六.线程池的正确关闭方式 七.线程池参数调优 一.线程池概述 1.线程池类 目前线程池类一般有两个 ...

  8. Java线程池(ExecutorService)使用

    一.前提 /** * 线程运行demo,运行时打出线程id以及传入线程中参数 */ public class ThreadRunner implements Runnable { private fi ...

  9. Java线程池的拒绝策略

    一.简介 jdk1.5 版本新增了JUC并发编程包,极大的简化了传统的多线程开发.前面文章中介绍了线程池的使用,链接地址:https://www.cnblogs.com/eric-fang/p/900 ...

随机推荐

  1. 关于button和submit的form提交以及 页面跳转问题

    最近在做官网的注册登录form提交时遇到了这个问题,1.0时因为使用普通的模板并没有出现页面跳转失败问题 由于2.0时更换了注册模板,此时按钮样式是以下样式 而在css样式的模板里使用的是button ...

  2. Spring Cloud实战 | 最终篇:Spring Cloud Gateway+Spring Security OAuth2集成统一认证授权平台下实现注销使JWT失效方案

    一. 前言 在上一篇文章介绍 youlai-mall 项目中,通过整合Spring Cloud Gateway.Spring Security OAuth2.JWT等技术实现了微服务下统一认证授权平台 ...

  3. 跟我一起学.NetCore之Swagger让前后端不再烦恼及界面自定义

    前言 随着前后端分离开发模式的流行,接口对接.联调成为常事,前端同事会经常问:我需要调哪个接口?这个接口数据格式是啥?条件都传啥? 对于一些紧急接口可能会采取沟通对接,然后补文档,其他的都会回一句:看 ...

  4. 使用内置对象Math.random实现猜数字游戏

    function getRandom(min,max){ return Math.floor(Math.random()*(max-min+1))+min;    //得到两个数之间的随机整数,包含实 ...

  5. Electron安装过程深入解析(读完此文解决Electron应用无法启动,无法打包的问题)

    1. 安装Electron依赖包 开发者往往通过npm install(或 yarn add)指令完成为Node.js工程安装依赖包的工作, 安装Electron也不例外,下面是npm和yarn的安装 ...

  6. 2020HC大会上,这群人在讨论云原生…

    启程 一年一度的华为全联接大会又开启了,伴随着一封来自华为全联接大会的邀请函,我来到了2020华为全联接大会的现场. 理解 今年,华为全联接大会的主题是:共 创 行 业 新 价 值!(NEW VALU ...

  7. C语言普通写法实现:针对多次同步失败的节能处理机制

    程序不美, 不来一一整理了. 以后有时间可以把这个功能封装为一个类的对象来操作.即使不封装为类,至少也该封装为一个独立的函数吧... 关键代码摘要如下: 无线同步信号发射端,每分钟发一次,每次发射的时 ...

  8. 【小白学PyTorch】18 TF2构建自定义模型

    [机器学习炼丹术]的炼丹总群已经快满了,要加入的快联系炼丹兄WX:cyx645016617 参考目录: 目录 1 创建自定义网络层 2 创建一个完整的CNN 2.1 keras.Model vs ke ...

  9. Java知识日常收集整理001Java获取变量的数据类型的实现方法

    一.具体情况区分 对于简单类型变量,是无法直接获得变量类型的:要想获取,必须自定义函数进行返回. 对于包装类型变量,是可以直接获得的,变量名称.getClass().getName(); 二.代码实现 ...

  10. 架构师根本不会被语言限制住,php照样可以用领域驱动设计DDD四层架构!

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 你在通往架构师的路上吗? 程序员这个行业就像是在不断的打怪升级,突破每一阶段的瓶颈期 ...