上一篇我們介紹了如何使用@Async注解來創建異步任務,我可以用這種方法來實作一些并發操作,以加速任務的執行效率,但是,如果只是如前文那樣直接簡單的創建來使用,可能還是會碰到一些問題,存在有什么問題呢?先來思考下,下面的這個介面,通過異步任務加速執行的實作,是否存在問題或風險呢?
@RestController
public class HelloController {
@Autowired
private AsyncTasks asyncTasks;
@GetMapping("/hello")
public String hello() {
// 將可以并行的處理邏輯,拆分成三個異步任務同時執行
CompletableFuture<String> task1 = asyncTasks.doTaskOne();
CompletableFuture<String> task2 = asyncTasks.doTaskTwo();
CompletableFuture<String> task3 = asyncTasks.doTaskThree();
CompletableFuture.allOf(task1, task2, task3).join();
return "Hello World";
}
}
雖然,從單次介面呼叫來說,是沒有問題的,但當介面被客戶端頻繁呼叫的時候,異步任務的數量就會大量增長:3 x n(n為請求數量),如果任務處理不夠快,就很可能會出現記憶體溢位的情況,那么為什么會記憶體溢位呢?根本原因是由于Spring Boot默認用于異步任務的執行緒池是這樣配置的:

圖中我標出的兩個重要引數是需要關注的:
queueCapacity:緩沖佇列的容量,默認為INT的最大值(2的31次方-1),maxSize:允許的最大執行緒數,默認為INT的最大值(2的31次方-1),
所以,默認情況下,一般任務佇列就可能把記憶體給堆滿了,所以,我們真正使用的時候,還需要對異步任務的執行執行緒池做一些基礎配置,以防止出現記憶體溢位導致服務不可用的問題,
配置默認執行緒池
默認執行緒池的配置很簡單,只需要在組態檔中完成即可,主要有以下這些引數:
spring.task.execution.pool.core-size=2
spring.task.execution.pool.max-size=5
spring.task.execution.pool.queue-capacity=10
spring.task.execution.pool.keep-alive=60s
spring.task.execution.pool.allow-core-thread-timeout=true
spring.task.execution.shutdown.await-termination=false
spring.task.execution.shutdown.await-termination-period=
spring.task.execution.thread-name-prefix=task-
具體配置含義如下:
spring.task.execution.pool.core-size:執行緒池創建時的初始化執行緒數,默認為8spring.task.execution.pool.max-size:執行緒池的最大執行緒數,默認為int最大值spring.task.execution.pool.queue-capacity:用來緩沖執行任務的佇列,默認為int最大值spring.task.execution.pool.keep-alive:執行緒終止前允許保持空閑的時間spring.task.execution.pool.allow-core-thread-timeout:是否允許核心執行緒超時spring.task.execution.shutdown.await-termination:是否等待剩余任務完成后才關閉應用spring.task.execution.shutdown.await-termination-period:等待剩余任務完成的最大時間spring.task.execution.thread-name-prefix:執行緒名的前綴,設定好了之后可以方便我們在日志中查看處理任務所在的執行緒池
動手試一試
我們直接基于之前chapter7-5的結果來進行如下操作,
首先,在沒有進行執行緒池配置之前,可以先執行一下單元測驗:
@Test
public void test1() throws Exception {
long start = System.currentTimeMillis();
CompletableFuture<String> task1 = asyncTasks.doTaskOne();
CompletableFuture<String> task2 = asyncTasks.doTaskTwo();
CompletableFuture<String> task3 = asyncTasks.doTaskThree();
CompletableFuture.allOf(task1, task2, task3).join();
long end = System.currentTimeMillis();
log.info("任務全部完成,總耗時:" + (end - start) + "毫秒");
}
由于默認執行緒池的核心執行緒數是8,所以3個任務會同時開始執行,日志輸出是這樣的:
2021-09-15 00:30:14.819 INFO 77614 --- [ task-2] com.didispace.chapter76.AsyncTasks : 開始做任務二
2021-09-15 00:30:14.819 INFO 77614 --- [ task-3] com.didispace.chapter76.AsyncTasks : 開始做任務三
2021-09-15 00:30:14.819 INFO 77614 --- [ task-1] com.didispace.chapter76.AsyncTasks : 開始做任務一
2021-09-15 00:30:15.491 INFO 77614 --- [ task-2] com.didispace.chapter76.AsyncTasks : 完成任務二,耗時:672毫秒
2021-09-15 00:30:19.496 INFO 77614 --- [ task-3] com.didispace.chapter76.AsyncTasks : 完成任務三,耗時:4677毫秒
2021-09-15 00:30:20.443 INFO 77614 --- [ task-1] com.didispace.chapter76.AsyncTasks : 完成任務一,耗時:5624毫秒
2021-09-15 00:30:20.443 INFO 77614 --- [ main] c.d.chapter76.Chapter76ApplicationTests : 任務全部完成,總耗時:5653毫秒
接著,可以嘗試在組態檔中增加如下的執行緒池配置
spring.task.execution.pool.core-size=2
spring.task.execution.pool.max-size=5
spring.task.execution.pool.queue-capacity=10
spring.task.execution.pool.keep-alive=60s
spring.task.execution.pool.allow-core-thread-timeout=true
spring.task.execution.thread-name-prefix=task-
日志輸出的順序會變成如下的順序:
2021-09-15 00:31:50.013 INFO 77985 --- [ task-1] com.didispace.chapter76.AsyncTasks : 開始做任務一
2021-09-15 00:31:50.013 INFO 77985 --- [ task-2] com.didispace.chapter76.AsyncTasks : 開始做任務二
2021-09-15 00:31:52.452 INFO 77985 --- [ task-1] com.didispace.chapter76.AsyncTasks : 完成任務一,耗時:2439毫秒
2021-09-15 00:31:52.452 INFO 77985 --- [ task-1] com.didispace.chapter76.AsyncTasks : 開始做任務三
2021-09-15 00:31:55.880 INFO 77985 --- [ task-2] com.didispace.chapter76.AsyncTasks : 完成任務二,耗時:5867毫秒
2021-09-15 00:32:00.346 INFO 77985 --- [ task-1] com.didispace.chapter76.AsyncTasks : 完成任務三,耗時:7894毫秒
2021-09-15 00:32:00.347 INFO 77985 --- [ main] c.d.chapter76.Chapter76ApplicationTests : 任務全部完成,總耗時:10363毫秒
- 任務一和任務二會馬上占用核心執行緒,任務三進入佇列等待
- 任務一完成,釋放出一個核心執行緒,任務三從佇列中移出,并占用核心執行緒開始處理
注意:這里可能有的小伙伴會問,最大執行緒不是5么,為什么任務三是進緩沖佇列,不是創建新執行緒來處理嗎?這里要理解緩沖佇列與最大執行緒間的關系:只有在緩沖佇列滿了之后才會申請超過核心執行緒數的執行緒來進行處理,所以,這里只有緩沖佇列中10個任務滿了,再來第11個任務的時候,才會在執行緒池中創建第三個執行緒來處理,這個這里就不具體寫列子了,讀者可以自己調整下引數,或者調整下單元測驗來驗證這個邏輯,
本系列教程《Spring Boot 2.x基礎教程》點擊直達!,歡迎收藏與轉發!如果學習程序中如遇困難?可以加入我們Spring技術交流群,參與交流與討論,更好的學習與進步!
代碼示例
本文的完整工程可以查看下面倉庫中2.x目錄下的chapter7-6工程:
- Github:https://github.com/dyc87112/SpringBoot-Learning/
- Gitee:https://gitee.com/didispace/SpringBoot-Learning/
如果您覺得本文不錯,歡迎Star支持,您的關注是我堅持的動力!
歡迎關注我的公眾號:程式猿DD,分享外面看不到的干貨與思考!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/300598.html
標籤:Java
上一篇:Java 掃描識別條形碼圖片
下一篇:Nginx常見問題
