<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 10990字,需瀏覽 22分鐘

           ·

          2021-09-18 12:06

          通過上一篇:配置@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),會有什么問題嗎?


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

          造成這種現(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注解中定義的taskExecutor1taskExecutor2就是線程池的名字。由于在第一步中,我們沒有具體寫兩個線程池Bean的名稱,所以默認(rèn)會使用方法名,也就是taskExecutor1taskExecutor2。

          第三步:寫個單元測試來驗證下,比如下面這樣:

          @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. 線程池1的三個任務(wù),task1和task2會先獲得執(zhí)行線程,然后task3因為沒有可分配線程進(jìn)入緩沖隊列
          2. 線程池2的三個任務(wù),task4和task5會先獲得執(zhí)行線程,然后task6因為沒有可分配線程進(jìn)入緩沖隊列
          3. 任務(wù)task3會在task1或task2完成之后,開始執(zhí)行
          4. 任務(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)注是我堅持的動力!


          往期推薦

          六成大學(xué)生認(rèn)為自己畢業(yè)10年內(nèi)會年入百萬!網(wǎng)友:知乎上多了,沒被社會毒打過吧!

          緩存核心知識小抄,面試必備,趕緊收藏!

          Java 17正式發(fā)布, Oracle宣布免費提供!“版本任你發(fā),我用Java 8”或成歷史?

          Spring Boot 中使用@Async實現(xiàn)異步調(diào)用,加速任務(wù)執(zhí)行!

          一個SpringMVC接口能返回JSON又能返回XML? 安排!


          技術(shù)交流群

          最近有很多人問,有沒有讀者交流群,想知道怎么加入。加入方式很簡單,有興趣的同學(xué),只需要點擊下方卡片,回復(fù)“加群,即可免費加入我們的高質(zhì)量技術(shù)交流群!

          點擊閱讀原文,直達(dá)教程目錄

          瀏覽 41
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  免费的黄色网 | 水密桃视频下载 | 在线看黄色视频网站 | 久久这里只有 | 91成人视频无码 |