@Async的異步任務(wù)多起來了,如何配置多個線程池來隔離任務(wù)?

通過上一篇:配置@Async異步任務(wù)的線程池的介紹,你應(yīng)該已經(jīng)了解到異步任務(wù)的執(zhí)行背后有一個線程池來管理執(zhí)行任務(wù)。為了控制異步任務(wù)的并發(fā)不影響到應(yīng)用的正常運作,我們必須要對線程池做好相應(yīng)的配置,防止資源的過渡使用。除了默認(rèn)線程池的配置之外,還有一類場景,也是很常見的,那就是多任務(wù)情況下的線程池隔離。
什么是線程池的隔離,為什么要隔離
可能有的小伙伴還不太了解什么是線程池的隔離,為什么要隔離?。所以,我們先來看看下面的場景案例:
@RestController
public class HelloController {
@Autowired
private 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接口,這兩個接口的具體執(zhí)行邏輯中都會把執(zhí)行過程拆分為三個異步任務(wù)來實現(xiàn)。
好了,思考一分鐘,想一下。如果這樣實現(xiàn),會有什么問題嗎?
造成這種現(xiàn)場的原因是:默認(rèn)情況下,所有用@Async創(chuàng)建的異步任務(wù)都是共用的一個線程池,所以當(dāng)有一些異步任務(wù)碰到性能問題的時候,是會直接影響其他異步任務(wù)的。
為了解決這個問題,我們就需要對異步任務(wù)做一定的線程池隔離,讓不同的異步任務(wù)互不影響。
不同異步任務(wù)配置不同線程池
下面,我們就來實際操作一下!
第一步:初始化多個線程池,比如下面這樣:
@EnableAsync
@Configuration
public class TaskPoolConfig {
@Bean
public 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;
}
@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;
}
}
注意:這里特地用
executor.setThreadNamePrefix設(shè)置了線程名的前綴,這樣可以方便觀察后面具體執(zhí)行的順序。
第二步:創(chuàng)建異步任務(wù),并指定要使用的線程池名稱
@Slf4j
@Component
public class AsyncTasks {
public static Random random = new Random();
@Async("taskExecutor1")
public CompletableFuture<String> doTaskOne(String taskNo) throws Exception {
log.info("開始任務(wù):{}", taskNo);
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
log.info("完成任務(wù):{},耗時:{} 毫秒", taskNo, end - start);
return CompletableFuture.completedFuture("任務(wù)完成");
}
@Async("taskExecutor2")
public CompletableFuture<String> doTaskTwo(String taskNo) throws Exception {
log.info("開始任務(wù):{}", taskNo);
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
log.info("完成任務(wù):{},耗時:{} 毫秒", taskNo, end - start);
return CompletableFuture.completedFuture("任務(wù)完成");
}
}
這里@Async注解中定義的taskExecutor1和taskExecutor2就是線程池的名字。由于在第一步中,我們沒有具體寫兩個線程池Bean的名稱,所以默認(rèn)會使用方法名,也就是taskExecutor1和taskExecutor2。
@Slf4j
@SpringBootTest
public class Chapter77ApplicationTests {
@Autowired
private AsyncTasks asyncTasks;
@Test
public void test() throws Exception {
long start = System.currentTimeMillis();
// 線程池1
CompletableFuture<String> task1 = asyncTasks.doTaskOne("1");
CompletableFuture<String> task2 = asyncTasks.doTaskOne("2");
CompletableFuture<String> task3 = asyncTasks.doTaskOne("3");
// 線程池2
CompletableFuture<String> task4 = asyncTasks.doTaskTwo("4");
CompletableFuture<String> task5 = asyncTasks.doTaskTwo("5");
CompletableFuture<String> task6 = asyncTasks.doTaskTwo("6");
// 一起執(zhí)行
CompletableFuture.allOf(task1, task2, task3, task4, task5, task6).join();
long end = System.currentTimeMillis();
log.info("任務(wù)全部完成,總耗時:" + (end - start) + "毫秒");
}
}
在上面的單元測試中,一共啟動了6個異步任務(wù),前三個用的是線程池1,后三個用的是線程池2。
先不執(zhí)行,根據(jù)設(shè)置的核心線程2和最大線程數(shù)2,來分析一下,大概會是怎么樣的執(zhí)行情況?
線程池1的三個任務(wù),task1和task2會先獲得執(zhí)行線程,然后task3因為沒有可分配線程進(jìn)入緩沖隊列 線程池2的三個任務(wù),task4和task5會先獲得執(zhí)行線程,然后task6因為沒有可分配線程進(jìn)入緩沖隊列 任務(wù)task3會在task1或task2完成之后,開始執(zhí)行 任務(wù)task6會在task4或task5完成之后,開始執(zhí)行
分析好之后,執(zhí)行下單元測試,看看是否是這樣的:
2021-09-15 23:45:11.369 INFO 61670 --- [ executor-1-1] com.didispace.chapter77.AsyncTasks : 開始任務(wù):1
2021-09-15 23:45:11.369 INFO 61670 --- [ executor-2-2] com.didispace.chapter77.AsyncTasks : 開始任務(wù):5
2021-09-15 23:45:11.369 INFO 61670 --- [ executor-2-1] com.didispace.chapter77.AsyncTasks : 開始任務(wù):4
2021-09-15 23:45:11.369 INFO 61670 --- [ executor-1-2] com.didispace.chapter77.AsyncTasks : 開始任務(wù):2
2021-09-15 23:45:15.905 INFO 61670 --- [ executor-2-1] com.didispace.chapter77.AsyncTasks : 完成任務(wù):4,耗時:4532 毫秒
2021-09-15 23:45:15.905 INFO 61670 --- [ executor-2-1] com.didispace.chapter77.AsyncTasks : 開始任務(wù):6
2021-09-15 23:45:18.263 INFO 61670 --- [ executor-1-2] com.didispace.chapter77.AsyncTasks : 完成任務(wù):2,耗時:6890 毫秒
2021-09-15 23:45:18.263 INFO 61670 --- [ executor-1-2] com.didispace.chapter77.AsyncTasks : 開始任務(wù):3
2021-09-15 23:45:18.896 INFO 61670 --- [ executor-2-2] com.didispace.chapter77.AsyncTasks : 完成任務(wù):5,耗時:7523 毫秒
2021-09-15 23:45:19.842 INFO 61670 --- [ executor-1-2] com.didispace.chapter77.AsyncTasks : 完成任務(wù):3,耗時:1579 毫秒
2021-09-15 23:45:20.551 INFO 61670 --- [ executor-1-1] com.didispace.chapter77.AsyncTasks : 完成任務(wù):1,耗時:9178 毫秒
2021-09-15 23:45:24.117 INFO 61670 --- [ executor-2-1] com.didispace.chapter77.AsyncTasks : 完成任務(wù):6,耗時:8212 毫秒
2021-09-15 23:45:24.117 INFO 61670 --- [ main] c.d.chapter77.Chapter77ApplicationTests : 任務(wù)全部完成,總耗時:12762毫秒
好了,今天的學(xué)習(xí)就到這里!更多Spring Boot教程可以點擊文末閱讀原文直達(dá)教程目錄!
代碼示例
本文的完整工程可以查看下面?zhèn)}庫中2.x目錄下的chapter7-7工程:
Github:https://github.com/dyc87112/SpringBoot-Learning/ Gitee:https://gitee.com/didispace/SpringBoot-Learning/
如果您覺得本文不錯,歡迎Star支持,您的關(guān)注是我堅持的動力!
往期推薦
技術(shù)交流群
最近有很多人問,有沒有讀者交流群,想知道怎么加入。加入方式很簡單,有興趣的同學(xué),只需要點擊下方卡片,回復(fù)“加群“,即可免費加入我們的高質(zhì)量技術(shù)交流群!
點擊閱讀原文,直達(dá)教程目錄
