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


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


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

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

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


衆所熟悉的同步代碼


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

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

html


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

而後把它注入到另外一個類裏進行調用,在調用時也輸出一下主線程信息。以下圖02:

web


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

面試


能夠看到這些代碼運行在主線程中,因此這些代碼的耗時操做會影響主線程。

首選的方案就是把耗時操做放入另外一個線程中執行(一般稱爲工做線程),把主線程解放出來。


同步代碼的異步化改造


因爲SpringBoot已經幫咱們作好了一切,只需按要求改造便可,只需兩步,真的是很是簡單。

第一步,引入啓用異步任務的註解,@EnableAsync,以下圖04:

spring


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

安全


這就行了,而後像普通方法同樣調用,以下圖06:

springboot


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

服務器


能夠看到主線程的id是1,並且瞬間執行完。任務在另外一個線程id爲17的線程中執行,且等耗時操做執行完後才結束。

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

主要是由於這個方法沒有返回值,若是有的話,只需改下返回類型便可

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

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

多線程


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

在同步代碼中,咱們原來return的是一個Object類型,顯然不知足需求,因此SpringBoot就想了一個辦法。

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

這個類就像一個「類型」佔位符同樣,若是你真正瞭解Java多線程的話就會明白,不然絕對不明白。

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

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

架構


輸出結果以下圖10:

app


能夠看到任務是在線程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年的碼農,如今任架構師。喜歡研究技術,崇尚簡單快樂。追求以通俗易懂的語言解說技術,但願全部的讀者都能看懂並記住。

    

原文出處:https://www.cnblogs.com/lixinjie/p/playing-springboot-009.html

相關文章
相關標籤/搜索