通过之前三篇关于Spring Boot异步任务实现的博文,我们分别学会了:

  • @Async创建异步任务

  • 为异步任务配置线程池

  • 多个线程池隔离不同的异步任务

今天我们继续对异步任务的实现进行完善和优化!

如果你已经看过上面几篇内容并已经掌握之后,一起来思考下面这个问题:

假设,线程池配置为核心线程数2、最大线程数2、缓冲队列长度2。此时,有5个异步任务同时开始,会发生什么?

场景重现

我们先来把上面的假设用代码实现一下:

第一步:创建Spring Boot应用,根据上面的假设写好线程池配置。

@EnableAsync
@SpringBootApplication
public class Chapter78Application {public static void main(String[] args) {SpringApplication.run(Chapter78Application.class, args);}@EnableAsync@Configurationclass TaskPoolConfig {@Beanpublic Executor taskExecutor1() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(2);executor.setMaxPoolSize(2);executor.setQueueCapacity(2);executor.setKeepAliveSeconds(60);executor.setThreadNamePrefix("executor-1-");return executor;}}}

第二步:用@Async注解实现一个部分任务

@Slf4j
@Component
public class AsyncTasks {public static Random random = new Random();@Async("taskExecutor1")public CompletableFuture<String> doTaskOne(String taskNo) throws Exception {log.info("开始任务:{}", taskNo);long start = System.currentTimeMillis();Thread.sleep(random.nextInt(10000));long end = System.currentTimeMillis();log.info("完成任务:{},耗时:{} 毫秒", taskNo, end - start);return CompletableFuture.completedFuture("任务完成");}}

第三步:编写测试用例

@Slf4j
@SpringBootTest
public class Chapter78ApplicationTests {@Autowiredprivate AsyncTasks asyncTasks;@Testpublic void test2() throws Exception {// 线程池配置:core-2,max-2,queue=2,同时有5个任务,出现下面异常:// org.springframework.core.task.TaskRejectedException: Executor [java.util.concurrent.ThreadPoolExecutor@59901c4d[Running, pool size = 2,// active threads = 0, queued tasks = 2, completed tasks = 4]] did not accept task: java.util.concurrent.CompletableFuture$AsyncSupply@408e96d9long start = System.currentTimeMillis();// 线程池1CompletableFuture<String> task1 = asyncTasks.doTaskOne("1");CompletableFuture<String> task2 = asyncTasks.doTaskOne("2");CompletableFuture<String> task3 = asyncTasks.doTaskOne("3");CompletableFuture<String> task4 = asyncTasks.doTaskOne("4");CompletableFuture<String> task5 = asyncTasks.doTaskOne("5");// 一起执行CompletableFuture.allOf(task1, task2, task3, task4, task5).join();long end = System.currentTimeMillis();log.info("任务全部完成,总耗时:" + (end - start) + "毫秒");}}

执行一下,可以类似下面这样的日志信息:

2021-09-22 17:33:08.159  INFO 21119 --- [   executor-1-2] com.didispace.chapter78.AsyncTasks       : 开始任务:2
2021-09-22 17:33:08.159  INFO 21119 --- [   executor-1-1] com.didispace.chapter78.AsyncTasks       : 开始任务:1org.springframework.core.task.TaskRejectedException: Executor [java.util.concurrent.ThreadPoolExecutor@3e1a3801[Running, pool size = 2, active threads = 2, queued tasks = 2, completed tasks = 0]] did not accept task: java.util.concurrent.CompletableFuture$AsyncSupply@64968732at org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.execute(ThreadPoolTaskExecutor.java:324)at java.util.concurrent.CompletableFuture.asyncSupplyStage(CompletableFuture.java:1604)at java.util.concurrent.CompletableFuture.supplyAsync(CompletableFuture.java:1830)at org.springframework.aop.interceptor.AsyncExecutionAspectSupport.doSubmit(AsyncExecutionAspectSupport.java:274)at org.springframework.aop.interceptor.AsyncExecutionInterceptor.invoke(AsyncExecutionInterceptor.java:129)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)at com.didispace.chapter78.AsyncTasks$$EnhancerBySpringCGLIB$$c7e8d57b.doTaskOne(<generated>)at com.didispace.chapter78.Chapter78ApplicationTests.test2(Chapter78ApplicationTests.java:51)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)...
Caused by: java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.CompletableFuture$AsyncSupply@64968732 rejected from java.util.concurrent.ThreadPoolExecutor@3e1a3801[Running, pool size = 2, active threads = 2, queued tasks = 2, completed tasks = 0]at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)at org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.execute(ThreadPoolTaskExecutor.java:321)... 74 more

从异常信息org.springframework.core.task.TaskRejectedException: Executor [java.util.concurrent.ThreadPoolExecutor@3e1a3801[Running, pool size = 2, active threads = 2, queued tasks = 2, completed tasks = 0]] did not accept task:中,可以明确知道:第5个任务因为超过了执行线程+缓冲队列长度,而被拒绝了。

所有,默认情况下,线程池的拒绝策略是:当线程池队列满了,会丢弃这个任务,并抛出异常。

配置拒绝策略

虽然线程池有默认的拒绝策略,但实际开发过程中,有些业务场景,直接拒绝的策略往往并不适用,有时候我们可能会选择舍弃最早开始执行而未完成的任务、也可能会选择舍弃刚开始执行而未完成的任务等更贴近业务需要的策略。所以,为线程池配置其他拒绝策略或自定义拒绝策略是很常见的需求,那么这个要怎么实现呢?

下面就来具体说说今天的正题,如何为线程池配置拒绝策略、如何自定义拒绝策略。

看下面这段代码的最后一行,setRejectedExecutionHandler方法就是为线程池设置拒绝策略的方法:

ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//...其他线程池配置executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());

ThreadPoolExecutor中提供了4种线程的策略可以供开发者直接使用,你只需要像下面这样设置即可:

// AbortPolicy策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());// DiscardPolicy策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());// DiscardOldestPolicy策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());// CallerRunsPolicy策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

这四个策略对应的含义分别是:

  • AbortPolicy策略:默认策略,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。

  • DiscardPolicy策略:如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常。

  • DiscardOldestPolicy策略:如果队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列。

  • CallerRunsPolicy策略:如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行。

而如果你要自定义一个拒绝策略,那么可以这样写:

executor.setRejectedExecutionHandler(new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {// 拒绝策略的逻辑}
});

当然如果你喜欢用Lamba表达式,也可以这样写:

executor.setRejectedExecutionHandler((r, executor1) -> {// 拒绝策略的逻辑
});

好了,今天的学习就到这里!更多Spring Boot教程可以点击文末阅读原文直达教程目录!

代码示例

本文的完整工程可以查看下面仓库中2.x目录下的chapter7-8工程:

  • Github:https://github.com/dyc87112/SpringBoot-Learning/

  • Gitee:https://gitee.com/didispace/SpringBoot-Learning/

如果您觉得本文不错,欢迎Star支持,您的关注是我坚持的动力!

往期推荐

Spring Boot如何实现在线预览?这个开源项目可以学习一下!

Spring Security太复杂?试试这个轻量、强大、优雅的权限认证框架!

来!一起搭建个永久运行的个人服务器吧!

笑出腹肌的注释,都是被代码耽误的诗人!

快速搭建Java 17环境并玩转Record特性

技术交流群

最近有很多人问,有没有读者交流群,想知道怎么加入。加入方式很简单,有兴趣的同学,只需要点击下方卡片,回复“加群“,即可免费加入我们的高质量技术交流群!

点击阅读原文,直达教程目录

任务数量超过线程池负荷了怎么办?拒绝策略安排起来!相关推荐

  1. 《Java线程池》:任务拒绝策略

    <Java线程池>:任务拒绝策略 转载:https://blog.csdn.net/u010412719/article/details/52132613 在没有分析线程池原理之前先来分析 ...

  2. 线程池的四种拒绝策略

    一.前言 线程池,相信很多人都有用过,没用过相信的也有学习过.但是,线程池的拒绝策略,相信知道的人会少许多. 二.四种线程池拒绝策略 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximum ...

  3. Java 线程池 ThreadPoolExecutor 八种拒绝策略浅析

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

  4. Java多线程学习七:线程池的 4 种拒绝策略和 6 种常见的线程池

    以便在必要的时候按照我们的策略来拒绝任务,那么拒绝任务的时机是什么呢?线程池会在以下两种情况下会拒绝新提交的任务. 第一种情况是当我们调用 shutdown 等方法关闭线程池后,即便此时可能线程池内部 ...

  5. 线程池的 RejectedExecutionHandler(拒绝策略)

    转载自  https://blog.csdn.net/jgteng/article/details/54411423 https://blog.csdn.net/luofenghan/article/ ...

  6. 【Java面试小短文】当任务数超过线程池的核心线程数,如何让它不进入阻塞队列直接启用最大数量的线程去执行任务?

    欢迎关注Java面试系列,不定期更新面试小短文.欢迎一键三连! 当任务数超过线程池的核心线程数,如何让它不进入阻塞队列直接启用最大数量的线程去执行任务? 当我们提交一个任务到线程池,它的工作原理如下: ...

  7. java 线程池数量_java线程池及创建多少线程合适

    java线程池 1.以下是ThreadPoolExecutor参数完备构造方法: public ThreadPoolExecutor(int corePoolSize,int maximumPoolS ...

  8. 易语言mysql线程池数量_线程池最佳线程数量到底要如何配置?

    前言 对应从事后端开发的同学来说,线程是必须要使用了,因为使用它可以提升系统的性能.但是,创建线程和销毁线程都是比较耗时的操作,频繁的创建和销毁线程会浪费很多CPU的资源.此外,如果每个任务都创建一个 ...

  9. 创建线程那么容易,为什么非要让我使用线程池?(深深深入剖析)

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 一.概述 1.问题 先看我们遇到的问题:我们创建线程的方式很简单, ...

最新文章

  1. 周信东c语言实验二实验报告,周信东主编最新版C语言程序设计基础实验一实验报告.doc...
  2. 12种主流编程语言输出“ Hello World ”,把我给难住了!
  3. 实操教程|详细记录solov2的ncnn实现和优化
  4. Android View体系(一)视图坐标系
  5. 桌面虚拟化“寻人行动”-转裁
  6. PhpCms V9调用指定栏目子栏目文章的两种方法
  7. MS SQL 2000 分配权限
  8. win10php环境配置教程,win10php环境搭建详细教程
  9. CodeBlocks注释和替换快捷键
  10. todo已完成任务_我已经完成了自己该做的任务用英文怎么
  11. 生物冰箱智能锁有哪些功能
  12. 图像在空域上的平滑处理
  13. 使用python生成文字视频
  14. 一战赚了1090亿,恐怖的头条CEO张一鸣!
  15. 网页中怎样在线播放音乐和视频
  16. 求阶乘求1!+2!+…+20!,其中x!=1*2*…*x,表示阶乘
  17. LTDC-DMA2D液晶显示 代码详解(二)
  18. 【数据分析可视化】通过apply进行数据预处理
  19. 2019阿里校招测评题 光明小学完全图最短路径问题
  20. 基于SSH的crm客户关系管理系统

热门文章

  1. rsync备份之windows+linux
  2. 【哈佛商学院和斯坦福要求学生必看的20部电影】中/英字幕
  3. linux echo 写二进制文件
  4. Docker的4种网络模式
  5. apt-get update,apt-get upgrade,apt-get dist-upgrade的作用
  6. 使用Windbg解析dump文件
  7. MFC ComboBox
  8. IRP和IO_STACK_LOCATION结构
  9. 排序算法--睡眠排序
  10. java设计模式---合成模式3