1. 程式人生 > >【玩轉SpringBoot】非同步任務執行與其執行緒池配置

【玩轉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:帝國的基石

相關推薦

SpringBoot非同步任務執行與其執行配置

同步程式碼寫起來簡單,但就是怕遇到耗時操作,會影響效率和吞吐量。此時非同步程式碼才是王者,但涉及多執行緒和執行緒池,以及非同步結果的獲取,寫起來頗為麻煩。不過在遇到SpringBoot非同步任務時,這個問題就不存在了。因為Spring家族是最替使用者考慮的。結果就是,像同步一樣簡單,像非同步一樣強大。眾所熟悉

SpringBoot配置檔案yml的正確開啟姿勢

序言在很久以前,Spring的配置檔案是基於XML的。它的名字就是applicationContext.xml,沒錯,就只有這一個xml檔案。它裡面配置了所有的東西。但是資料庫資訊通常會單獨拿出來,放入一個properties檔案,通常叫db.properties。後來覺著一個xml裡的東西實在太多了,就按功

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

自動配置隱含兩層含義,要搞清楚上帝讓程式設計師的髮量減少,是為了讓他變得更聰明,如果有一天聰明到了極點,那就是絕頂聰明。據說在大腦高速運轉下,這樣更有利於散熱,不至於核心溫度過高而產生告警。聰明的大腦是用來思考的,現在就來深入思考和分析下自動配置。自動配置包含兩層意思,一個是配置,一個是自動。這不廢話嘛。配置

SpringBoot給自動配置來個整體大揭祕

  上一篇文章中提到的條件註解,只是自動配置整體解決方案中的一個環節而已,可以說是管中窺豹。本文就逐步擦除迷霧,讓整體浮現出來,這樣就會有一個巨集觀的認識。除了寫程式碼之外,還能幹點什麼?提到“配置”這個詞,我們不一定知道它是什麼,但絕對知道它不是什麼,顯然,不是寫程式碼。

SpringBoot看似複雜的Environment其實很簡單

喜歡寫程式碼,討厭配環境我相信這十個字的小標題代表了大多數碼農的心聲。十年前讀大學時,學校開設了C語言還有C++。但是學習這兩種語言,對於新手來說非常沒有成就感。於是我就在校門口買個光碟,裝個VS(宇宙第一IDE),還有離線中文版MSDN(最牛的幫助文件),萬事已俱備。學習C#語法,看類的API,然後從Win

SpringBoot翻身做主人,一統web伺服器

寄人籬下的日子一直以來受傳統影響,我們的web工程總是打成war包,然後放入tomcat的webapps目錄下面。如下圖01: 當tomcat啟動時,會去解壓war包,然後執行web工程。這大家都非常熟悉了。用一個抽象的圖形表示,就是這樣子。如下圖02: 在一個大大的tomcat裡面,有一個小

SpringBoot讓錯誤處理重新由web伺服器接管

其實web伺服器是會處理錯誤的在web.xml還是隨處可見的年代時(確實有點老黃曆了),下面的這些配置應該都不陌生。根據錯誤程式碼處理錯誤,如下圖01: 根據異常型別處理錯誤,如下圖02: 不過我們更加熟悉的應該是SpringMVC的統一異常處理。如下圖03: 看到@Controlle

SpringBootSpringBoot應用的啟動過程一覽表

SpringBoot應用的啟動方式很簡單,就一行程式碼,如下圖01: 其實這行程式碼背後主要執行兩個方法,一個是構造方法,一個是run方法。構造方法主要內容就是收集一些資料,和確認一些資訊。如下圖02: 真正的執行要從run方法開始,為此,SpringBoot特意定義了一個監聽器,專門監聽這個

SpringBoot通過事件機制參與SpringBoot應用的啟動過程

生命週期和事件監聽一個應用的啟動過程和關閉過程是歸屬到“生命週期”這個概念的範疇。典型的設計是在啟動和關閉過程中會觸發一系列的“事件”,我們只要監聽這些事件,就能參與到這個過程中來。要想監聽事件,首先得有事件監聽器,就是常說的Listener。下面就是Sprin

開源BananaPi R2 —— 第二篇 Openwrt 網口配置分析

sign ati arr asi 1.0 tran spa 們的 errors 上次和大家分享了如何燒錄和安裝Openwrt到BananaPi R2,運行Openwrt的R2目前就具備路由器的功能了,這次我們來看看R2運行Openwrt的性能如何,同時也會講解一些常

開源Linux C 檢測網口熱插拔

int NetDetect(char *net_name, int *statue) { int ret = 0; int skfd = 0; struct ifreq ifr; skfd = socket(AF_INET, SOCK_DGRAM, 0); //建議s

SpringBoot之定時任務詳解

pac multi mpi 例如 mark tor tro size har 序言 使用SpringBoot創建定時任務非常簡單,目前主要有以下三種創建方式: 一、基於註解(@Scheduled) 二、基於接口(SchedulingConfigurer) 前者相信

ExcelOracle PLSQL處理生成XLSX檔案

INTRODUCTION介紹    之前發表了一個研究心得(當然是站在別人的肩膀上的),在Oracle中直接用PL/SQL解析並讀取Excel的內容。很多人都感興趣,按照我的寫法也可以成功實現了。不過,有很多朋友提出了另外一個要求:讀取Excel是可以了,那是否可以在Ora

開源制作Docker鏡像

沒有 名稱 登錄 我們 nan utils str oar image 做嵌入式方向經常會遇到的一個問題,就是編譯環境安裝,如果換電腦,再重新安裝環境是一個比較費時的事情,這個時候可以自己制作一個Docker鏡像,然後把編譯環境在Docker鏡像裏面配置好,以後同步環境就非

Ubuntu11. Ubuntu上的瀏覽檔案命令nautilus

瀏覽檔案命令nautilus 先看一下它的幫助命令 [email protected]:~$ nautilus -h 用法: nautilus [選項...] [URI...] Browse the file system with the fil

GridView之簡單實現隱藏列

    做專案中有時候,如果設定了gridview隱藏列,問題就變得很簡單,所以小編總結了兩種實驗過的方法分享給大家。 第一種.     在gridview的RowCreated的方法中設定需要隱藏的

GridView之TemplateField模板

背景介紹:     近期負責開發的子系統中,很大一部分的工作都是和GridView打交道,各種編輯、刪除gridview的操作,所以小編準備系列總結,來進一步學習。     在介紹詳情之前,讓小編帶大家瞭解幾個重要角色。    介紹:在gridview 中單個欄位都會採用

GridView之隱藏域問題

   接著上篇部落格【GridView設定隱藏列方法】來說,這次小編將介紹一種更加方便的方法: 設定隱藏域:    在Gridview加一列使用模板,在模板裡定義隱藏域HiddenField控制元件,

Golangslice切片的操作——切片的追加、刪除、插入等

一、一般操作   1,宣告變數,go自動初始化為nil,長度:0,地址:0,nil func main(){ var ss []string; fmt.Printf("length:%v \taddr:%p \tisnil:%v",len(ss),ss, ss==nil) } -

開源BananaPi R2——移植RPi.GPIO 到 R2

機會 tin 循環輸出 nal 腳本 evel 3.1 我們 api 1. 首先給大家介紹一下什麽是RPi.GPIO. 簡單去講,RPi.GPIO就是一個運行在樹莓派開發板上可以通過Python去控制GPIO的一個中間件。 現在我這邊做了一個基礎功能的移