前言

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

线程池工作机制

这里用一个例子来描述下线程池的工作机制,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. 每日一个知识点:Volatile 和 CAS 的弊端之总线风暴

    每日一个知识点系列的目的是针对某一个知识点进行概括性总结,可在一分钟内完成知识点的阅读理解,此处不涉及详细的原理性解读. 一.什么是总线风暴 总线风暴,听着真是一个帅气的词语,但如果发生在你的系统上那 ...

  2. Java递归算法经典实例(兔子问题、阶乘、1到100累加)

    https://blog.csdn.net/isitman/article/details/61199070

  3. RabbitMQ小记(四)

    1.RabbitMQ管理 (1)权限管理 物理服务器和虚拟主机都各自有独立的权限管理,用户访问需要设置权限. 授权命令:rabbitmqctl set permissions [-p vhost] { ...

  4. 单元测试框架怎么搭?快来看看新版Junit5的这些神奇之处吧!

    为什么使用JUnit5 JUnit4被广泛使用,但是许多场景下使用起来语法较为繁琐,JUnit5中支持lambda表达式,语法简单且代码不冗余. JUnit5易扩展,包容性强,可以接入其他的测试引擎. ...

  5. You must give at least one requirement to install (see "pip help install")

    语言: python why? install 后面没有参数,也就是说没有给想要安装的包 way? pip install 后面要跟想要安装的包名

  6. C语言中 malloc

    参考:https://blog.csdn.net/kokodudu/article/details/11760863 一.malloc()和free()的基本概念以及基本用法: 1.函数原型及说明: ...

  7. 安装、验证安装 Oracle Database XE 11gR2

    操作系统:Windows 10 x64 第一节:下载 Oracle Database XE 11gR2 第二节:安装.验证安装 Oracle Database XE 11gR2 第三节:Oracle ...

  8. 二进制部署Redis-5.07

    Redis简介 Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理. 它支持字符串.哈希表.列表.集合.有序集合,位图,hyperloglogs等数据类 ...

  9. Python+Appium自动化测试(9)-自动选择USB用于传输文件(不依赖appium对手机页面元素进行定位)

    一,问题 app自动化测试使用Android真机连接电脑时,通常会遇到两种情况: 1.测试机连接电脑会弹窗提示USB选项,选择USB用于"传输文件",有些手机不支持设置默认USB选 ...

  10. dict, hash

    dict: dictKey -- > dictVal example: dictEntry *dictFind(dict *d, const void *key)     Key is like ...