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

前言

為了控制異步任務(wù)的并發(fā)不影響到應(yīng)用的正常運(yùn)作,我們必須要對(duì)線程池做好相應(yīng)的配置,防止資源的過(guò)渡使用。除了默認(rèn)線程池的配置之外,還有一類(lèi)場(chǎng)景,也是很常見(jiàn)的,那就是多任務(wù)情況下的線程池隔離。

什么是線程池的隔離,為什么要隔離

可能有的小伙伴還不太了解什么是線程池的隔離,為什么要隔離?。所以,我們先來(lái)看看下面的場(chǎng)景案例:

@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 "";
    }
    
}

上面的代碼中,有兩個(gè)API接口,這兩個(gè)接口的具體執(zhí)行邏輯中都會(huì)把執(zhí)行過(guò)程拆分為三個(gè)異步任務(wù)來(lái)實(shí)現(xiàn)。
好了,思考一分鐘,想一下。如果這樣實(shí)現(xiàn),會(huì)有什么問(wèn)題嗎?


上面這段代碼,在API請(qǐng)求并發(fā)不高,同時(shí)如果每個(gè)任務(wù)的處理速度也夠快的時(shí)候,是沒(méi)有問(wèn)題的。但如果并發(fā)上來(lái)或其中某幾個(gè)處理過(guò)程扯后腿了的時(shí)候。這兩個(gè)提供不相干服務(wù)的接口可能會(huì)互相影響。比如:假設(shè)當(dāng)前線程池配置的最大線程數(shù)有2個(gè),這個(gè)時(shí)候/api-1接口中task1和task2處理速度很慢,阻塞了;那么此時(shí),當(dāng)用戶(hù)調(diào)用api-2接口的時(shí)候,這個(gè)服務(wù)也會(huì)阻塞!

造成這種現(xiàn)場(chǎng)的原因是:默認(rèn)情況下,所有用@Async創(chuàng)建的異步任務(wù)都是共用的一個(gè)線程池,所以當(dāng)有一些異步任務(wù)碰到性能問(wèn)題的時(shí)候,是會(huì)直接影響其他異步任務(wù)的。

為了解決這個(gè)問(wèn)題,我們就需要對(duì)異步任務(wù)做一定的線程池隔離,讓不同的異步任務(wù)互不影響。

不同異步任務(wù)配置不同線程池

下面,我們就來(lái)實(shí)際操作一下!

  • 第一步:初始化多個(gè)線程池,比如下面這樣:
@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ù),并指定要使用的線程池名稱(chēng)
@Slf4j
@Component
public class AsyncTasks {

    public static Random random = new Random();

    @Async("taskExecutor1")
    public CompletableFuture<String> doTaskOne(String taskNo) throws Exception {
        log.info("開(kāi)始任務(wù):{}", taskNo);
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("完成任務(wù):{},耗時(shí):{} 毫秒", taskNo, end - start);
        return CompletableFuture.completedFuture("任務(wù)完成");
    }

    @Async("taskExecutor2")
    public CompletableFuture<String> doTaskTwo(String taskNo) throws Exception {
        log.info("開(kāi)始任務(wù):{}", taskNo);
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("完成任務(wù):{},耗時(shí):{} 毫秒", taskNo, end - start);
        return CompletableFuture.completedFuture("任務(wù)完成");
    }

}

這里@Async注解中定義的taskExecutor1taskExecutor2就是線程池的名字。由于在第一步中,我們沒(méi)有具體寫(xiě)兩個(gè)線程池Bean的名稱(chēng),所以默認(rèn)會(huì)使用方法名,也就是taskExecutor1taskExecutor2。

  • 第三步:寫(xiě)個(gè)單元測(cè)試來(lái)驗(yàn)證下,比如下面這樣:
@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ù)全部完成,總耗時(shí):" + (end - start) + "毫秒");
    }

}

在上面的單元測(cè)試中,一共啟動(dòng)了6個(gè)異步任務(wù),前三個(gè)用的是線程池1,后三個(gè)用的是線程池2。

先不執(zhí)行,根據(jù)設(shè)置的核心線程2和最大線程數(shù)2,來(lái)分析一下,大概會(huì)是怎么樣的執(zhí)行情況?

1、線程池1的三個(gè)任務(wù),task1和task2會(huì)先獲得執(zhí)行線程,然后task3因?yàn)闆](méi)有可分配線程進(jìn)入緩沖隊(duì)列
2、線程池2的三個(gè)任務(wù),task4和task5會(huì)先獲得執(zhí)行線程,然后task6因?yàn)闆](méi)有可分配線程進(jìn)入緩沖隊(duì)列
3、任務(wù)task3會(huì)在task1或task2完成之后,開(kāi)始執(zhí)行
4、任務(wù)task6會(huì)在task4或task5完成之后,開(kāi)始執(zhí)行

分析好之后,執(zhí)行下單元測(cè)試,看看是否是這樣的:

2021-09-15 23:45:11.369  INFO 61670 --- [   executor-1-1] com.didispace.chapter77.AsyncTasks       : 開(kāi)始任務(wù):1
2021-09-15 23:45:11.369  INFO 61670 --- [   executor-2-2] com.didispace.chapter77.AsyncTasks       : 開(kāi)始任務(wù):5
2021-09-15 23:45:11.369  INFO 61670 --- [   executor-2-1] com.didispace.chapter77.AsyncTasks       : 開(kāi)始任務(wù):4
2021-09-15 23:45:11.369  INFO 61670 --- [   executor-1-2] com.didispace.chapter77.AsyncTasks       : 開(kāi)始任務(wù):2
2021-09-15 23:45:15.905  INFO 61670 --- [   executor-2-1] com.didispace.chapter77.AsyncTasks       : 完成任務(wù):4,耗時(shí):4532 毫秒
2021-09-15 23:45:15.905  INFO 61670 --- [   executor-2-1] com.didispace.chapter77.AsyncTasks       : 開(kāi)始任務(wù):6
2021-09-15 23:45:18.263  INFO 61670 --- [   executor-1-2] com.didispace.chapter77.AsyncTasks       : 完成任務(wù):2,耗時(shí):6890 毫秒
2021-09-15 23:45:18.263  INFO 61670 --- [   executor-1-2] com.didispace.chapter77.AsyncTasks       : 開(kāi)始任務(wù):3
2021-09-15 23:45:18.896  INFO 61670 --- [   executor-2-2] com.didispace.chapter77.AsyncTasks       : 完成任務(wù):5,耗時(shí):7523 毫秒
2021-09-15 23:45:19.842  INFO 61670 --- [   executor-1-2] com.didispace.chapter77.AsyncTasks       : 完成任務(wù):3,耗時(shí):1579 毫秒
2021-09-15 23:45:20.551  INFO 61670 --- [   executor-1-1] com.didispace.chapter77.AsyncTasks       : 完成任務(wù):1,耗時(shí):9178 毫秒
2021-09-15 23:45:24.117  INFO 61670 --- [   executor-2-1] com.didispace.chapter77.AsyncTasks       : 完成任務(wù):6,耗時(shí):8212 毫秒
2021-09-15 23:45:24.117  INFO 61670 --- [           main] c.d.chapter77.Chapter77ApplicationTests  : 任務(wù)全部完成,總耗時(shí):12762毫秒
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容