Spring @Async异步方法中的线程隔离
目录转载说明
https://www.cnblogs.com/didispace/p/15307355.html
内容摘要
上一篇分享中介绍了Spring中的异步方法的使用方法,文中提到,如果我们不显示创建线程池,异步方法会放到Spring默认提供的线程池SimpleAsyncTaskExecutor
中执行, 但是需要注意的是,Spring提供的这个默认线程池的队列长度是没有限制的,也就是说,如果一直往默认线程池你提交任务有可能OOM的风险,所以一般情况都建议使用自己创建的线程池。
本文提到的线程隔离,则是定义多个线程池,它的使用场景是,如果是完全两类独立的业务,业务A比较耗时,另外一个业务B处理速度很快,如果使用同一个线程池,就会拉低业务B的任务处理效率。所以如果需要解决这个问题就需要让异步方法指派到不同的线程池,以便让不同的异步业务互相不影响。
代码实例
第一步:创建线程池
@EnableAsync
@Configuration
public class ThreadPoolConfig {
@Bean
public Executor taskExecutor1(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(10);
executor.setKeepAliveSeconds(60);
//用executor.setThreadNamePrefix设置了线程名的前缀
executor.setThreadNamePrefix("executor-1-");
// 如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
@Bean
public 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;
}
}
第二步:编写异步方法
这里@Async
注解中定义的taskExecutor1
和taskExecutor2
就是线程池的名字。由于在第一步中,我们没有具体写两个线程池Bean的名称,所以默认会使用方法名,也就是taskExecutor1
和taskExecutor2
。
@Service
@Slf4j
public class AsyncService {
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("任务完成");
}
}
第三步:测试观察
在上面的单元测试中,一共启动了6个异步任务,前三个用的是线程池1,后三个用的是线程池2。
@SpringBootTest
class AsyncDemoApplicationTests {
@Test
void contextLoads() {
}
@Autowired
AsyncService service;
@Test
public void test01() throws Exception{
// 线程池1
CompletableFuture<String> task1 = service.doTaskOne("1");
CompletableFuture<String> task2 = service.doTaskOne("2");
CompletableFuture<String> task3 = service.doTaskOne("3");
// 线程池2
CompletableFuture<String> task4 = service.doTaskTwo("4");
CompletableFuture<String> task5 = service.doTaskTwo("5");
CompletableFuture<String> task6 = service.doTaskTwo("6");
// 一起执行
CompletableFuture.allOf(task1, task2, task3, task4, task5, task6).join();
}
}
INFO 5524 --- [ executor-1-1] c.l.a.asyncdemo.service.AsyncService : 开始任务:1
INFO 5524 --- [ executor-2-2] c.l.a.asyncdemo.service.AsyncService : 开始任务:5
INFO 5524 --- [ executor-1-2] c.l.a.asyncdemo.service.AsyncService : 开始任务:2
INFO 5524 --- [ executor-2-1] c.l.a.asyncdemo.service.AsyncService : 开始任务:4
INFO 5524 --- [ executor-2-1] c.l.a.asyncdemo.service.AsyncService : 完成任务:4,耗时:614 毫秒
INFO 5524 --- [ executor-2-1] c.l.a.asyncdemo.service.AsyncService : 开始任务:6
INFO 5524 --- [ executor-2-2] c.l.a.asyncdemo.service.AsyncService : 完成任务:5,耗时:3188 毫秒
INFO 5524 --- [ executor-1-2] c.l.a.asyncdemo.service.AsyncService : 完成任务:2,耗时:5314 毫秒
INFO 5524 --- [ executor-1-2] c.l.a.asyncdemo.service.AsyncService : 开始任务:3
INFO 5524 --- [ executor-2-1] c.l.a.asyncdemo.service.AsyncService : 完成任务:6,耗时:5539 毫秒
INFO 5524 --- [ executor-1-1] c.l.a.asyncdemo.service.AsyncService : 完成任务:1,耗时:6622 毫秒
INFO 5524 --- [ executor-1-2] c.l.a.asyncdemo.service.AsyncService : 完成任务:3,耗时:6136 毫秒