【玩轉SpringBoot】異步任務執行與其線程池配置



同步代碼寫起來簡單,但就是怕遇到耗時操作,會影響效率和吞吐量。


此時異步代碼才是王者,但涉及多線程和線程池,以及異步結果的獲取,寫起來頗為麻煩。

不過在遇到SpringBoot異步任務時,這個問題就不存在了。因為Spring家族是最替用戶考慮的。

結果就是,像同步一樣簡單,像異步一樣強大


眾所熟悉的同步代碼


先准備一些代碼,為了模擬耗時操作,在其中加入線程睡眠語句。

同時打印出運行這些代碼的線程信息。如下圖01:


其中一個是沒有返回值的,一個是有返回值的。

然后把它注入到另一個類里進行調用,在調用時也輸出一下主線程信息。如下圖02:


下面是輸出結果,如下圖03:


可以看到這些代碼運行在主線程中,所以這些代碼的耗時操作會影響主線程。

首選的方案就是把耗時操作放入另一個線程中執行(通常稱為工作線程),把主線程解放出來。


同步代碼的異步化改造


由於SpringBoot已經幫我們做好了一切,只需按要求改造即可,只需兩步,真的是非常簡單。

第一步,引入啟用異步任務的注解,@EnableAsync,如下圖04:


第二步,在原來的方法上標上@Async注解,如下圖05:


這就好了,然后像普通方法一樣調用,如下圖06:


看下輸出結果,如下圖07:


可以看到主線程的id是1,而且瞬間執行完。任務在另一個線程id為17的線程中執行,且等耗時操作執行完后才結束。

代碼完全不變,只需加兩個注解,同步立馬變成異步啦。簡直爽歪歪了。

主要是因為這個方法沒有返回值,如果有的話,只需改下返回類型即可

SpringBoot一共支持三種返回類型,來逐一看下。

第一種,返回類型為Java的Future<?>,如下圖08:


熟悉Java多線程的朋友對這個類都應該不陌生。為了代碼能正常編譯,在方法最后需要return一個這樣的類型。

在同步代碼中,我們原來return的是一個Object類型,顯然不滿足需求,所以SpringBoot就想了一個辦法。

新增了一個類,AsyncResult,使用它進行類型適配,這也是此類的主要作用,保證編譯通過。

這個類就像一個“類型”占位符一樣,如果你真正了解Java多線程的話就會明白,否則絕對不明白。

然后就像普通方法調用一樣調用它,接着通過while循環等待異步任務完成后,輸出返回結果。

注意,我特意輸出了一下方法調用返回的future變量,如下圖09:


輸出結果如下圖10:


可以看到任務是在線程id為17的線程中執行,主線程不斷睡眠等待,直到任務完成后才獲取到任務的返回結果。

重要時刻來臨,可以看到我們輸出的future變量類型是Java的FutureTask類,而我們實際在代碼中return的是Spring的AsyncResult類。

是不是很奇怪呢?其實一點都不怪,這和Java多線程有關,如果還不明白的話,后面有說明。

第二種,返回類型為Spring的ListenableFuture<?>,如下圖11:


可以看到代碼在return的時候寫法是一樣的,那這個類型的好處是什么呢?答案是可以注冊回調

有了回調,任務在完成后會自動執行回調代碼,所以主線程就不用等了。

因此在調用時要注冊回調代碼,包括成功回調和失敗回調,如下圖12:


注意,我們同樣打印一下方法返回變量listenableFuture的類型。

輸出結果如下圖13:


可以看到此時主線程瞬間執行完畢。任務在線程id為17的線程中執行,完成后執行了回調,且在同一個線程中。

同樣變量listenableFuture的類型是Spring的ListenableFutureTask類,並不是我們在代碼里return的AsyncResult類。

第三種,返回類型為Java的CompletableFuture<?>,如下圖14:


這個類型是Java 8新增的,可以對異步任務進行特殊的操作。

然后進行調用,同樣輸出下返回變量類型,如下圖15:


輸出結果如下圖16:


輸出內容很容易看懂。重點看下返回變量的類型,它就是Java的CompletableFuture<?>類。

那我們在代碼中return的是什么類型呢?如下圖17:


可以看到和真實調用時返回的還是不一樣。如果還不明白,下面來說明下。

Spring在遇到標有@Async的方法時會生成代理,代理做的事情就是把該方法包裝成一個任務submit到線程池中。

在submit的時候會返回真正的返回值,就是上面我們在調用方法時輸出的。

而我們在寫@Async方法代碼時return的是一個類似類型占位符的類,它的一個作用就是保證編譯通過。

另一個作用就是傳遞返回值,在任務執行完成時,把值往外層傳遞。


線程池的個性化按需配置


對於Java來說,幾乎所有的異步執行代碼都是提交到線程池中來執行的,因為線程池可以管理好線程,我們就不用操心了。

不過我們依然可以對線程池進行配置,如核心線程數、最大線程數、內部隊列長度等等。

SpringBoot當然也支持這些配置,按照慣例,這些配置也是放在application.yml配置文件中的。

一些IDE是可以進行自動提示的,如下圖18:


這些配置的前綴是spring.task.execution,主要包括三類配置,線程池中線程的數目和隊列的大小,線程池關閉時的行為,線程名稱的前綴。

有求知欲的朋友可能會尋思,這些配置究竟是如何生效的呢?下面就來滿足一下好奇心,其實很簡單。

SpringBoot的特性之一就是自動配置,這些自動配置代碼都位於這個jar包中,如下圖19:


這個jar包名稱很容易記住,所以最好都能記住,下次有疑問自己就可以去找了。

我們在這個jar包里尋找和任務(task)相關的包名稱,如下圖20:


前兩個類是和任務執行相關的,其中以Properties結尾的類是用於存放application.yml里面的配置的。

AutoConfiguration結尾的類是用於自動配置的,主要是bean定義的注冊。

這種寫法是SpringBoot自動配置的標准模式,可以看看其它的,都是這樣的。

看下TaskExecutionProperties類,如下圖21:


指定好前綴后,配置文件中的配置項和類中的屬性完全是一一對應的,而且類中屬性可以有默認值,這樣配置文件中沒有配置時就使用默認值。

再來看下TaskExecutionAutoConfiguration類,這里面就注冊了兩個bean,如下圖22:


首先使用剛剛的屬性注冊一個TaskExecutorBuilder類型的bean,然后再使用它注冊一個ThreadPoolTaskExecutor類型的bean。

其實異步任務執行主要是要找到一個線程池的bean,來完成任務的提交,具體尋找邏輯的如下:

1)如果容器中存在唯一一個TaskExecutor類型的bean,那就用它。否則繼續往下。

2)如果容器中存在一個名稱為taskExecutor且類型為Executor的bean,就用它,否則繼續往下。

3)將使用SimpleAsyncTaskExecutor類進行異步方法調用。


void異步方法的異常處理


需要注意的是,返回類型為void的異步方法,將不會向調用者傳遞異常。默認情況下,這些未捕獲的異常僅僅輸出一下日志。

所以對於void方法一定要自己處理好異常。如果恰巧沒處理好,怎么辦呢?不要着急。

SpringBoot提供了統一的未捕獲異常處理方式,只要實現一個接口即可,如下圖23:


我們可以獲取到拋出的異常,還有拋出異常時執行的異步方法,還有調用該異步方法時傳入的參數。

那么,對於有返回值的異步方法,則本身可以傳遞異常,所以不會使用這種方式。這一點需注意。


作者寄語


異步方法的原理很簡單,就是在單獨的線程中執行一個方法或代碼片段。

不過有兩方面需要注意,技術方面和業務方面:

技術方面

1)如何獲取異步方法的返回值

2)如何處理異步方法產生的異常

3)如何處理異步方法超時的問題

業務方面:

1)異步方法執行成功時對業務的影響

2)異步方法拋出異常時對業務的影響

3)異步方法執行超時時對業務的影響

 

(END)

 

>>> 玩轉SpringBoot系列文章 <<<

 

【玩轉SpringBoot】配置文件yml的正確打開姿勢

【玩轉SpringBoot】用好條件相關注解,開啟自動配置之門

【玩轉SpringBoot】給自動配置來個整體大揭秘

【玩轉SpringBoot】看似復雜的Environment其實很簡單

【玩轉SpringBoot】翻身做主人,一統web服務器

【玩轉SpringBoot】讓錯誤處理重新由web服務器接管

【玩轉SpringBoot】SpringBoot應用的啟動過程一覽表

【玩轉SpringBoot】通過事件機制參與SpringBoot應用的啟動過程

 

>>> 品Spring系列文章 <<<

 

品Spring:帝國的基石

品Spring:bean定義上梁山

品Spring:實現bean定義時采用的“先進生產力”

品Spring:注解終於“成功上位”

品Spring:能工巧匠們對注解的“加持”

品Spring:SpringBoot和Spring到底有沒有本質的不同?

品Spring:負責bean定義注冊的兩個“排頭兵”

品Spring:SpringBoot輕松取勝bean定義注冊的“第一階段”

品Spring:SpringBoot發起bean定義注冊的“二次攻堅戰”

品Spring:注解之王@Configuration和它的一眾“小弟們”

品Spring:bean工廠后處理器的調用規則

品Spring:詳細解說bean后處理器

品Spring:對@PostConstruct和@PreDestroy注解的處理方法

品Spring:對@Resource注解的處理方法

品Spring:對@Autowired和@Value注解的處理方法

品Spring:真沒想到,三十步才能完成一個bean實例的創建

品Spring:關於@Scheduled定時任務的思考與探索,結果尷尬了

 

>>> 熱門文章集錦 <<<

 

畢業10年,我有話說

【面試】我是如何面試別人List相關知識的,深度有點長文

我是如何在畢業不久只用1年就升為開發組長的

爸爸又給Spring MVC生了個弟弟叫Spring WebFlux

【面試】我是如何在面試別人Spring事務時“套路”對方的

【面試】Spring事務面試考點吐血整理(建議珍藏)

【面試】我是如何在面試別人Redis相關知識時“軟懟”他的

【面試】吃透了這些Redis知識點,面試官一定覺得你很NB(干貨 | 建議珍藏)

【面試】如果你這樣回答“什么是線程安全”,面試官都會對你刮目相看(建議珍藏)

【面試】迄今為止把同步/異步/阻塞/非阻塞/BIO/NIO/AIO講的這么清楚的好文章(快快珍藏)

【面試】一篇文章幫你徹底搞清楚“I/O多路復用”和“異步I/O”的前世今生(深度好文,建議珍藏)

【面試】如果把線程當作一個人來對待,所有問題都瞬間明白了

Java多線程通關———基礎知識挑戰

品Spring:帝國的基石

 

作者是工作超過10年的碼農,現在任架構師。喜歡研究技術,崇尚簡單快樂。追求以通俗易懂的語言解說技術,希望所有的讀者都能看懂並記住。

    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM