同步代碼寫起來簡單,但就是怕遇到耗時操做,會影響效率和吞吐量。
此時異步代碼纔是王者,但涉及多線程和線程池,以及異步結果的獲取,寫起來頗爲麻煩。
不過在遇到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】用好條件相關注解,開啓自動配置之門
【玩轉SpringBoot】看似複雜的Environment其實很簡單
【玩轉SpringBoot】讓錯誤處理從新由web服務器接管
【玩轉SpringBoot】SpringBoot應用的啓動過程一覽表
【玩轉SpringBoot】經過事件機制參與SpringBoot應用的啓動過程
>>> 品Spring系列文章 <<<
品Spring:SpringBoot和Spring到底有沒有本質的不一樣?
品Spring:SpringBoot輕鬆取勝bean定義註冊的「第一階段」
品Spring:SpringBoot發起bean定義註冊的「二次攻堅戰」
品Spring:註解之王@Configuration和它的一衆「小弟們」
品Spring:對@PostConstruct和@PreDestroy註解的處理方法
品Spring:對@Autowired和@Value註解的處理方法
品Spring:真沒想到,三十步才能完成一個bean實例的建立
品Spring:關於@Scheduled定時任務的思考與探索,結果尷尬了
>>> 熱門文章集錦 <<<
爸爸又給Spring MVC生了個弟弟叫Spring WebFlux
【面試】吃透了這些Redis知識點,面試官必定以爲你很NB(乾貨 | 建議珍藏)
【面試】若是你這樣回答「什麼是線程安全」,面試官都會對你另眼相看(建議珍藏)
【面試】迄今爲止把同步/異步/阻塞/非阻塞/BIO/NIO/AIO講的這麼清楚的好文章(快快珍藏)
【面試】一篇文章幫你完全搞清楚「I/O多路複用」和「異步I/O」的前世此生(深度好文,建議珍藏)
做者是工做超過10年的碼農,如今任架構師。喜歡研究技術,崇尚簡單快樂。追求以通俗易懂的語言解說技術,但願全部的讀者都能看懂並記住。
原文出處:https://www.cnblogs.com/lixinjie/p/playing-springboot-009.html