当前位置: 首页 > 后端技术 > Java

SpringBoot中有多个@Async异步任务时,记得隔离线程池!

时间:2023-04-01 23:49:35 Java

通过上一篇文章:配置@Async异步任务的线程池介绍,大家应该已经了解到异步任务执行的背后有一个线程池来管理执行任务。为了在不影响应用程序正常运行的情况下控制异步任务的并发,我们必须对线程池进行相应的配置,以防止过度使用资源。除了默认线程池的配置,还有一类场景,也是很常见的,那就是多任务情况下的线程池隔离。什么是线程池隔离,为什么要隔离?有的朋友可能不明白什么是线程池隔离,为什么要隔离?.那么,让我们来看看下面的场景:@RestControllerpublicclassHelloController{@AutowiredprivateAsyncTasksasyncTasks;@GetMapping("/api-1")publicStringtaskOne(){CompletableFuturetask1=asyncTasks.doTaskOne("1");CompletableFuturetask2=asyncTasks.doTaskOne("2");CompletableFuturetask3=asyncTasks.doTaskOne("3");CompletableFuture.allOf(task1,task2,task3).join();返回””;}@GetMapping("/api-2")publicStringtaskTwo(){CompletableFuturetask1=asyncTasks.doTaskTwo("1");CompletableFuturetask2=asyncTasks.doTaskTwo("2");CompletableFuturetask3=asyncTasks.doTaskTwo("3");CompletableFuture.allOf(task1,task2,task3).join();返回””;在上面的代码中,有两个API接口,这两个接口在具体的执行逻辑中,会把执行过程分成三个异步任务来执行。好吧,想一想,想一想。如果这样实施,会不会有什么问题?上面的代码在API请求并发不高的情况下,如果每个任务的处理速度够快的话,是可以的。但是如果并发上来或者某些处理过程受阻。这两个提供不相关服务的接口可能会相互干扰。例如:假设当前线程池配置的最大线程数为2,此时/api-1接口中的task1和task2处理速度很慢,阻塞;那么此时用户调用api-2接口时,服务也会阻塞!出现这个场景的原因是:默认情况下,所有使用@Async创建的异步任务共享一个线程池,所以当一些异步任务遇到性能问题时,会直接影响其他异步任务。为了解决这个问题,我们需要对异步任务做一定的线程池隔离,让不同的异步任务不互相影响。不同的异步任务配置不同的线程池,来实践一下吧!第一步:初始化多个线程池,比如下面这样:executor.setCorePoolSize(2);executor.setMaxPoolSize(2);executor.setQueueCapacity(10);executor.setKeepAliveSeconds(60);executor.setThreadNamePrefix("executor-1-");executor.setRejectedExecutionHandler(newThreadPoolExecutor.CallerRunsPolicy());回归执行人;{ThreadPoolTask??Executor执行器=newThreadPoolTask??Executor();executor.setCorePoolSize(2);executor.setMaxPoolSize(2);executor.setQueueCapacity(10);executor.setKeepAliveSeconds(60);executor.setThreadNamePrefix("executor-2-");.setRejectedExecutionHandler(newThreadPoolExecutor.CallerRunsPolicy());返回执行人;}}注意:这里特地用executor.setThreadNamePrefix设置了线程名的前缀,方便后面观察具体的执行顺序第二步:创建一个异步任务并指定要使用的线程池名称@Slf4j@ComponentpublicclassAsyncTasks{publicstaticRandomrandom=newRandom();@Async("taskExecutor1")publicCompletableFuturedoTaskOne(StringtaskNo)throwsException{log.info("开始任务:{}",taskNo);长启动=System.currentTimeMillis();Thread.sleep(random.nextInt(10000));长端=System.currentTimeMillis();日志。info("完成任务:{},耗时:{}毫秒",taskNo,end-start);returnCompletableFuture.completedFuture("任务完成");}@Async("taskExecutor2")publicCompletableFuturedoTaskTwo(StringtaskNo)throwsException{log.info("开始任务:{}",taskNo);长启动=System.currentTimeMillis();Thread.sleep(random.nextInt(10000));长端=System.currentTimeMillis();log.info("完成任务:{},耗时:{}毫秒",taskNo,end-start);returnCompletableFuture.completedFuture("任务完成");}}这里taskExecutor1和taskEx在@Async注解中定义ecutor2是线程池的名称。由于第一步我们没有具体写两个线程池bean的名字,所以会默认使用方法名,即taskExecutor1和taskExecutor2。第三步:写一个单元测试来验证,比如下面这样:@Slf4j@SpringBootTestpublicclassChapter77ApplicationTests{@AutowiredprivateAsyncTasksasyncTasks;@Testpublicvoidtest()throwsException{longstart=System.currentTimeMillis();//线程池1CompletableFuturetask1=asyncTasks.doTaskOne("1");CompletableFuturetask2=asyncTasks.doTaskOne("2");CompletableFuturetask3=asyncTasks.doTaskOne("3");//线程池2CompletableFuturetask4=asyncTasks.doTaskTwo("4");CompletableFuturetask5=asyncTasks.doTaskTwo("5");CompletableFuturetask6=asyncTasks.doTaskTwo("6");//执行CompletableFuture.allOf(task1,task2,task3,task4,task5,task6).join();长端=System.currentTimeMillis();log.info("所有任务完成,总耗时:"+(end-start)+"毫秒");}}上述单元测试中,一共启动了6个异步任务,前三个使用线程池1,后三个使用线程池2。我们先不执行它。根据设置的核心线程2和最大线程数2,我们来分析一下。会是怎样的处决?对于线程池1中的三个任务,task1和task2会先得到执行线程,然后task3会进入线程池2中三个任务的缓冲队列,task4和task5会先得到执行线程,然后task6会得到执行线程,因为没有可分配的线程,线程进入缓冲队列。task3会在task1或task2完成后开始执行task。task6将在task4或task5完成后执行。分析完成后,执行下一个单元测试,看看是不是这样::45:11.369INFO61670---[executor-1-1]com.didispace.chapter77.AsyncTasks:Startingtask:12021-09-1523:45:11.369INFO61670---[executor-2-2]com.didispace.chapter77.AsyncTasks:开始任务:52021-09-1523:45:11.369INFO61670---[executor-2-1]com.didispace.chapter77.AsyncTasks:开始任务:42021-09-1523:45:11.369INFO61670---[executor-1-2]com.didispace.chapter77.AsyncTasks:开始任务:22021-09-1523:45:15.905INFO61670---[executor-2-1]com。didispace.chapter77.AsyncTasks:完成的任务:4,花费的时间:4532毫秒2021-09-1523:45:15.905INFO61670---[executor-2-1]com.didispace.chapter77.AsyncTasks:开始的任务:62021-09-1523:45:18.263INFO61670---[executor-1-2]com.didispace.chapter77.AsyncTasks:完成的任务:2,花费的时间:6890毫秒2021-09-1523:45:18.263INFO61670---[executor-1-2]com.didispace.chapter77.AsyncTasks:开始的任务:32021-09-1523:45:18.896INFO61670---[executor-2-2]com.didispace.chapter77.AsyncTasks:完成的任务:5in7523ms2021-09-1523:45:19.842INFO61670---[executor-1-2]com.didispace.chapter77.AsyncTasks:完成的任务:3,花费:1579ms2021-09-1523:45:20.551INFO61670---[executor-1-1]com.didispace.chapter77.AsyncTasks:已完成的任务:1,花费的时间:9178毫秒2021-09-1523:45:24.117INFO61670---[executor-2-1]com.didispace.chapter77.AsyncTasks:已完成的任务:6.Time-consuming:8212milliseconds2021-09-1523:45:24.117INFO61670---[main]c.d.chapter77.Chapter77ApplicationTests:所有任务完成,总耗时:12762毫秒好了,今天的学习就到这里啦!如果你在学习过程中遇到困难?可以加入我们超优质的Spring技术交流群,参与交流讨论,更好的学习进步!更多SpringBoot教程可直接点击!,欢迎收藏和转发支持!代码示例本文的完整工程可以在下面仓库2.x目录下的chapter7-7工程中查看:Github:https://github.com/dyc87112/SpringBoot-Learning/Gitee:https://gitee.com/didispace/SpringBoot-Learning/如果觉得本文不错,欢迎Star支持,您的关注是我坚持的动力!欢迎关注我的公众号:程序员DD,分享别处看不到的知识和思考