@Async的异步任务多起来了,如何配置多个线程池来隔离任务?
通过上一篇:配置@Async异步任务的线程池的介绍,你应该已经了解到异步任务的执行背后有一个线程池来管理执行任务。为了控制异步任务的并发不影响到应用的正常运作,我们必须要对线程池做好相应的配置,防止资源的过渡使用。除了默认线程池的配置之外,还有一类场景,也是很常见的,那就是多任务情况下的线程池隔离。
什么是线程池的隔离,为什么要隔离
可能有的小伙伴还不太了解什么是线程池的隔离,为什么要隔离?。所以,我们先来看看下面的场景案例:
@RestController
public class HelloController {@Autowiredprivate AsyncTasks asyncTasks;@GetMapping("/api-1")public String taskOne() {CompletableFuture<String> task1 = asyncTasks.doTaskOne("1");CompletableFuture<String> task2 = asyncTasks.doTaskOne("2");CompletableFuture<String> task3 = asyncTasks.doTaskOne("3");CompletableFuture.allOf(task1, task2, task3).join();return "";}@GetMapping("/api-2")public String taskTwo() {CompletableFuture<String> task1 = asyncTasks.doTaskTwo("1");CompletableFuture<String> task2 = asyncTasks.doTaskTwo("2");CompletableFuture<String> task3 = asyncTasks.doTaskTwo("3");CompletableFuture.allOf(task1, task2, task3).join();return "";}}
上面的代码中,有两个API接口,这两个接口的具体执行逻辑中都会把执行过程拆分为三个异步任务来实现。
好了,思考一分钟,想一下。如果这样实现,会有什么问题吗?
上面这段代码,在API请求并发不高,同时如果每个任务的处理速度也够快的时候,是没有问题的。但如果并发上来或其中某几个处理过程扯后腿了的时候。这两个提供不相干服务的接口可能会互相影响。比如:假设当前线程池配置的最大线程数有2个,这个时候/api-1接口中task1和task2处理速度很慢,阻塞了;那么此时,当用户调用api-2接口的时候,这个服务也会阻塞!
造成这种现场的原因是:默认情况下,所有用@Async
创建的异步任务都是共用的一个线程池,所以当有一些异步任务碰到性能问题的时候,是会直接影响其他异步任务的。
为了解决这个问题,我们就需要对异步任务做一定的线程池隔离,让不同的异步任务互不影响。
不同异步任务配置不同线程池
下面,我们就来实际操作一下!
第一步:初始化多个线程池,比如下面这样:
@EnableAsync
@Configuration
public class TaskPoolConfig {@Beanpublic Executor taskExecutor1() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(2);executor.setMaxPoolSize(2);executor.setQueueCapacity(10);executor.setKeepAliveSeconds(60);executor.setThreadNamePrefix("executor-1-");executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());return executor;}@Beanpublic Executor taskExecutor2() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(2);executor.setMaxPoolSize(2);executor.setQueueCapacity(10);executor.setKeepAliveSeconds(60);executor.setThreadNamePrefix("executor-2-");executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());return executor;}
}
注意:这里特地用
executor.setThreadNamePrefix
设置了线程名的前缀,这样可以方便观察后面具体执行的顺序。
第二步:创建异步任务,并指定要使用的线程池名称
@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("任务完成");}@Async("taskExecutor2")public CompletableFuture<String> doTaskTwo(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("任务完成");}}
这里@Async
注解中定义的taskExecutor1
和taskExecutor2
就是线程池的名字。由于在第一步中,我们没有具体写两个线程池Bean的名称,所以默认会使用方法名,也就是taskExecutor1
和taskExecutor2
。
第三步:写个单元测试来验证下,比如下面这样:
@Slf4j
@SpringBootTest
public class Chapter77ApplicationTests {@Autowiredprivate AsyncTasks asyncTasks;@Testpublic void test() throws Exception {long start = System.currentTimeMillis();// 线程池1CompletableFuture<String> task1 = asyncTasks.doTaskOne("1");CompletableFuture<String> task2 = asyncTasks.doTaskOne("2");CompletableFuture<String> task3 = asyncTasks.doTaskOne("3");// 线程池2CompletableFuture<String> task4 = asyncTasks.doTaskTwo("4");CompletableFuture<String> task5 = asyncTasks.doTaskTwo("5");CompletableFuture<String> task6 = asyncTasks.doTaskTwo("6");// 一起执行CompletableFuture.allOf(task1, task2, task3, task4, task5, task6).join();long end = System.currentTimeMillis();log.info("任务全部完成,总耗时:" + (end - start) + "毫秒");}}
在上面的单元测试中,一共启动了6个异步任务,前三个用的是线程池1,后三个用的是线程池2。
先不执行,根据设置的核心线程2和最大线程数2,来分析一下,大概会是怎么样的执行情况?
线程池1的三个任务,task1和task2会先获得执行线程,然后task3因为没有可分配线程进入缓冲队列
线程池2的三个任务,task4和task5会先获得执行线程,然后task6因为没有可分配线程进入缓冲队列
任务task3会在task1或task2完成之后,开始执行
任务task6会在task4或task5完成之后,开始执行
分析好之后,执行下单元测试,看看是否是这样的:
2021-09-15 23:45:11.369 INFO 61670 --- [ executor-1-1] com.didispace.chapter77.AsyncTasks : 开始任务:1
2021-09-15 23:45:11.369 INFO 61670 --- [ executor-2-2] com.didispace.chapter77.AsyncTasks : 开始任务:5
2021-09-15 23:45:11.369 INFO 61670 --- [ executor-2-1] com.didispace.chapter77.AsyncTasks : 开始任务:4
2021-09-15 23:45:11.369 INFO 61670 --- [ executor-1-2] com.didispace.chapter77.AsyncTasks : 开始任务:2
2021-09-15 23:45:15.905 INFO 61670 --- [ executor-2-1] com.didispace.chapter77.AsyncTasks : 完成任务:4,耗时:4532 毫秒
2021-09-15 23:45:15.905 INFO 61670 --- [ executor-2-1] com.didispace.chapter77.AsyncTasks : 开始任务:6
2021-09-15 23:45:18.263 INFO 61670 --- [ executor-1-2] com.didispace.chapter77.AsyncTasks : 完成任务:2,耗时:6890 毫秒
2021-09-15 23:45:18.263 INFO 61670 --- [ executor-1-2] com.didispace.chapter77.AsyncTasks : 开始任务:3
2021-09-15 23:45:18.896 INFO 61670 --- [ executor-2-2] com.didispace.chapter77.AsyncTasks : 完成任务:5,耗时:7523 毫秒
2021-09-15 23:45:19.842 INFO 61670 --- [ executor-1-2] com.didispace.chapter77.AsyncTasks : 完成任务:3,耗时:1579 毫秒
2021-09-15 23:45:20.551 INFO 61670 --- [ executor-1-1] com.didispace.chapter77.AsyncTasks : 完成任务:1,耗时:9178 毫秒
2021-09-15 23:45:24.117 INFO 61670 --- [ executor-2-1] com.didispace.chapter77.AsyncTasks : 完成任务:6,耗时:8212 毫秒
2021-09-15 23:45:24.117 INFO 61670 --- [ main] c.d.chapter77.Chapter77ApplicationTests : 任务全部完成,总耗时:12762毫秒
好了,今天的学习就到这里!更多Spring Boot教程可以点击文末阅读原文直达教程目录!
代码示例
本文的完整工程可以查看下面仓库中2.x
目录下的chapter7-7
工程:
Github:https://github.com/dyc87112/SpringBoot-Learning/
Gitee:https://gitee.com/didispace/SpringBoot-Learning/
如果您觉得本文不错,欢迎Star
支持,您的关注是我坚持的动力!
往期推荐
六成大学生认为自己毕业10年内会年入百万!网友:知乎上多了,没被社会毒打过吧!
缓存核心知识小抄,面试必备,赶紧收藏!
Java 17正式发布, Oracle宣布免费提供!“版本任你发,我用Java 8”或成历史?
Spring Boot 中使用@Async实现异步调用,加速任务执行!
一个SpringMVC接口能返回JSON又能返回XML? 安排!
技术交流群
最近有很多人问,有没有读者交流群,想知道怎么加入。加入方式很简单,有兴趣的同学,只需要点击下方卡片,回复“加群“,即可免费加入我们的高质量技术交流群!
点击阅读原文,直达教程目录
@Async的异步任务多起来了,如何配置多个线程池来隔离任务?相关推荐
- @Async 异步任务自定义线程池的配置方法和 @Scheduled 定时任务自定义线程池的配置方式
文章目录 一.定时和异步业务场景描述 二.定时调度任务的实现方式 三.定时调度任务的问题描述 四.定时调度多线程解决方案(方案一) 五.异步多线程程序实现方式 六.定时调度多线程解决方案(方案二) 一 ...
- async spring 默认线程池_springboot-@Async默认线程池导致OOM问题
转 springboot-@Async默认线程池导致OOM问题 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 前言: 1.最近项目上在测试人员压 ...
- @async 默认线程池_springboot@Async默认线程池导致OOM问题
地址:http://suo.im/5Y3RGF 作者:ignorewho 前言: 最近项目上在测试人员压测过程中发现了OOM问题,项目使用springboot搭建项目工程,通过查看日志中包含信息:un ...
- @async 默认线程池_Springboot线程池的使用和扩展
点击上方"Java知音",选择"置顶公众号" 技术文章第一时间送达! 作者:程序员欣宸 https://blog.csdn.net/boling_cavalry ...
- async spring 默认线程池_SpringBoot中Async异步方法和定时任务介绍
1.功能说明 Spring提供了Async注解来实现方法的异步调用.即当调用Async标识的方法时,调用线程不会等待被调用方法执行完成即返回继续执行以下操作,而被调用的方法则会启动一个独立线程来执行此 ...
- 20.案例实战:为@Async实现一个自定义线程池
代码:https://github.com/NIGHTFIGHTING/spring_boot_learning/tree/master/19-20/agan-boot/agan-boot-async ...
- SpringBoot线程池ThreadPoolTaskExecutor和@Async异步方法浅解及代码应用示例
目录 线程池执行顺序 线程池配置策略 Spring线程池的配置类: Spring有.无返回值的异步调用示例 自定义的异步方法类代码: 测试类代码 常见问题: 参考文章: 线程池执行顺序 核心线程数(C ...
- .NET 线程 Thread 进程 Process、线程池 pool、Invoke、begininvoke、异步回调、APM、EAP、TPL、aysnc、await
windows系统是一个多线程的操作系统.一个程序至少有一个进程,一个进程至少有一个线程.进程是线程的容器,一个C#客户端程序开始于一个单独的线程,CLR(公共语言运行库)为该进程创建了一个线程,该线 ...
- python线程池原理_Django异步任务线程池实现原理
这篇文章主要介绍了Django异步任务线程池实现原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 当数据库数据量很大时(百万级),许多批量数据修改 ...
最新文章
- 一文尽揽2018 Google I/O:谷歌让你感受到AI科技的魅力
- MySQL分库分表使用Snowflake全局ID生成器(3rd)
- 半导体基础知识(3):双极结和场效应晶体管(BJT和FET)
- OPKG 软件包管理
- 色彩空间DCI XYZ转RGB
- MySQL Innodb存储引擎使用B+树做索引的优点
- 查看在Ubuntu上打印的大型JSON文件
- 【Go学习笔记2】go语言中的基本数据类型和包的介绍(一)
- Nginx在嵌入式系统中的应用
- access转sql iif_ACCESS 中的IIF使用
- 【python】编程语言入门经典100例--23
- Pytorch demo(三)之蚂蚁和蜜蜂
- 手把手教你绘制最基础的列线图
- Minimum Flips to Make a OR b Equal to c(C++ 或运算的最小翻转次数)
- 学妹面试拼刀刀被问Java策略模式是什么鬼?哈哈哈哈
- py文件编译为pyc(命令与脚本)
- 微信小程序利用百度api达成植物识别
- 瞎子摸象---汇兑损益
- Excel公式大全 excel自动求减 15个常用excel函数公式
- 给对象做的暖心微信公众号推送(可自定义信息和天气预报的城市)