詳解SpringBoot如何開啟異步編程
一、什么是異步?
現(xiàn)在我們假設(shè)有一個接口方法,里面又調(diào)用了三個子方法,分別是A,B,C。先從A執(zhí)行,執(zhí)行完畢再執(zhí)行B,B執(zhí)行完最后執(zhí)行C。這也是我們代碼最常見的執(zhí)行方式。ABC順序執(zhí)行,其中一個出問題了,如果拋出了異常,后續(xù)則不再執(zhí)行。這中方式就是同步執(zhí)行。
那么異步執(zhí)行是什么樣子的呢?
假設(shè)B方法改為異步,那么A方法執(zhí)行完畢,執(zhí)行B方法。此時不需要等B方法執(zhí)行完畢,代碼會直接執(zhí)行C方法。也就是B方法不再影響C方法的執(zhí)行,這里B方法就是異步執(zhí)行。
二、為什么要使用異步編程?
異步方法的作用也很明顯,假設(shè)我們上面的接口方法是一個用戶注冊方法,A方法注冊成功,B方法是增加積分,C方法增加權(quán)限。那么A方法執(zhí)行成功后,我們就可以給用戶添加權(quán)限了。至于增加積分,完全可以異步處理,這樣注冊的效率就會更高,提高用戶體驗。實際上的業(yè)務(wù)場景還有很多,通常需要異步的都是執(zhí)行比較慢,又對我們串行執(zhí)行的業(yè)務(wù)邏輯沒有影響,為了提高代碼效率,我們就需要異步執(zhí)行。
三、SpringBoot開啟異步編程
接下來,我們就以SpringBoot項目為例,來看下如何使用異步編程。
第一步,通常來說一個新功能都是從引入包開始,因為這個功能本身是Spring 3提供的功能,所以我們可以直接使用。首先在主啟動類上通過注解@EnableAsync開啟異步功能。
@SpringBootApplication @EnableAsync public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
第二步,在需要異步的方法上,添加注解@Async,這樣就可以了。
@Async public void testAsync() { xxxx; }
是不是非常簡單?使用當(dāng)然非常簡單了,但是這里面有許多坑和業(yè)務(wù)場景,我們就來一一說明。
1、有返回值的異步方法
@RestController public class TestController { public Result testAsync(){ service.testA(); service.testB(); service.testC(); return Result.success(); } } @Service public class service{ void testA(){ xxxx; }; @Async void testB(){ xxxx; }; void testC(){ xxxx; }; }
我們在Controller層的testAsync接口中,分別調(diào)用了service層中的testA,testB,testC方法。其中testB我們加了@Async異步注解,那么在主線程testAsync中執(zhí)行testB就無需等待,會繼續(xù)執(zhí)行testC方法。
如果是沒有返回值的testB方法,那么這樣就可以滿足我們的需求了,但是如果testB有返回值呢,而且需要我們對返回值進(jìn)行存儲。那么就需要有返回值的異步方法。這個也比較簡單,我們只需要使用Future(java.util類中的)來進(jìn)行返回。
@RestController public class TestController { public Result testAsync(){ service.testA(); Future future = service.testB(); //testC不許等待testB執(zhí)行完畢,即可執(zhí)行 service.testC(); //get()會一直等待有結(jié)果返回 log.info(future.get()); return Result.success(); } } @Service public class service{ void testA(){ xxxx; }; @Async Future<String> testB(){ xxxx; return new AsyncResult<>("返回值結(jié)果); }; void testC(){ xxxx; }; }
看testB方法,我們改為Future類型的返回。此時就可以獲得異步方法的返回了,但是注意我們這里是異步,所以在使用返回結(jié)果future時,可能是沒有值的,因為我們異步方法可能沒有執(zhí)行完畢。這里需要我們使用future.get()方法獲取返回結(jié)果,get方法會自旋一直等待,直到有返回結(jié)果才繼續(xù)往下執(zhí)行。
這里使用get獲取結(jié)果時要注意了,如果放在testC前面獲取異步結(jié)果,就會導(dǎo)致testC無法串行執(zhí)行,所以一定要根據(jù)業(yè)務(wù)場景來使用。比如我們將獲取testB的結(jié)果放在了最后一行,testC串行執(zhí)行,在最終返回結(jié)果前再獲取異步testB方法的返回結(jié)果。
2、@Async使用的線程池
在使用@Async注解時,你一定從網(wǎng)上看到過許多人說,不要使用默認(rèn)線程池,這個是怎么回事呢?
首先,我們要明白,@Async的異步實現(xiàn),其實就是起一個新的線程,和主線程區(qū)分開,來執(zhí)行異步的方法。這樣你主線程繼續(xù)串行執(zhí)行,我新起的子線程執(zhí)行你的異步方法,互不影響,這樣就實現(xiàn)了異步執(zhí)行的目的。
牽扯到多線程,那就一定離不開線程池的使用,這個在這里不詳細(xì)說,我會在線程池相關(guān)的文章里說明?;氐絼偛诺膯栴},網(wǎng)上許多人說的,不要使用默認(rèn)線程池,是因為@Async以前版本使用的默認(rèn)線程池是SimpleAsyncTaskExecutor,這個線程池有許多問題,推薦使用的都是ThreadPoolTaskExecutor。
實際上,SpringBoot已經(jīng)考慮到這個問題了,在新的版本中@Async默認(rèn)使用的線程池就是ThreadPoolTaskExecutor,這里注意看下你使用的版本。
SpringBoot 2.0.9以及之前的版本,使用的線程池默認(rèn)是SimpleAsyncTaskExecutor,之后默認(rèn)使用的是ThreadPoolTaskExecutor
3、SpringBoot使用的默認(rèn)線程池源碼解析
SpringBoot默認(rèn)使用的是哪個線程池,是如何配置的呢?這一部分,感興趣的可以看下,不感興趣的直接看下一節(jié),配置線程池參數(shù)就可以了。
找到@Async的攔截器方法,每次會先執(zhí)行。
入?yún)⒌腅xcutor參數(shù)可以是null,如果不是null,就使用指定的Excutor,這個就可以來指定我們自己的Excutor,這個后面說。
如果入?yún)⑹莕ull,就使用如下方法獲取。這里有三行關(guān)鍵代碼,第一行通過beanFactory.getBean獲取TaskExcutor類型的Bean。但是這里是通過類型獲取的,實現(xiàn)Excutor的如果有多個類,就會報錯,被第二行代碼捕獲,進(jìn)入第三行代碼獲取Bean。第三行通過名字獲取的Bean,名字是taskExcutor。
名字是taskExcutor的Bean,是SpringBoot默認(rèn)給我們配置好了的,接著看SpringBoot的自動配置相關(guān)源碼TaskExecutionAutoConfiguration類,紅框里最終配置的Bean名字就是taskExcutor。
這里注意這個注解@ConditionalOnMissingBean,只在沒有Excutor實現(xiàn)類的時候,才會默認(rèn)配置。和上面通過beanFactory.getBean獲取TaskExcutor類型的Bean代碼呼應(yīng)起來了。如果我們項目沒有注入Excutor的話,SpringBoot就會默認(rèn)注入ThreadPoolTaskExecutor。如果我們指定了,這樣使用@Async(“myExcutor”)則使用我們指定的。如果沒指定,還有多個實現(xiàn)Bean沖突了,則使用Bean名字是taskExcutor的。
我們再來看下默認(rèn)線程池配置,創(chuàng)建線程池時使用的都是TaskExecutionProperties這個配置文件。
我們再來看這個配置文件,紅框處是幾個關(guān)鍵參數(shù)。
問題非常明顯,有幾個參數(shù)值最大值非常大,Integer.MAX_VALUE。需要我們在使用時根據(jù)業(yè)務(wù)場景進(jìn)行配置,不然存在隱患。
4、線程池配置
首先,我們需要了解線程池的基本運(yùn)行原理,才能更好的配置。這里簡單給大家介紹一下,在后面專門的線程池文章會詳細(xì)說明。
線程池有幾個重要的基本參數(shù),核心線程數(shù),等待隊列,最大線程數(shù)量,線程存活時間等。有新的任務(wù)過來時,先啟用核心線程,核心線程滿了后,再加到等待隊列,等待隊列也滿了后,如果小于最大線程數(shù),則繼續(xù)開啟新的線程,直到達(dá)到最大線程數(shù)量。
所以,上文線程池默認(rèn)配置中,系統(tǒng)默認(rèn)配置的核心線程數(shù)是8,但是等待隊列最大值無上限,這里就不太合適。需要按照業(yè)務(wù)實際使用場景來配置。同時最大線程數(shù)也需要配置,不然資源不夠,起了無限多的線程,就OOM了。
存活時間默認(rèn)60s,也是根據(jù)自己的任務(wù)響應(yīng)時間來配置。allowCoreThreadTimeout是配置核心線程數(shù)是否設(shè)置存活時間的參數(shù),如果配置true,那么核心線程數(shù)存活時間也會生效,空閑時不會一直存在。
配置時可以自己通過代碼設(shè)置,也可以直接在配置文件設(shè)置,下面是簡單參考。
spring: task: execution: pool: core-size: 8 queue-capacity: 100 max-size: 16 keep-alive: 60 allow-core-thread-timeout: false
四、結(jié)語
到這,我們今天的文章就算結(jié)束了,大家跟著文章,應(yīng)該學(xué)會使用異步編程了,能夠滿足大多數(shù)場景的使用了。
當(dāng)然,還有些許多問題因為篇幅問題這里沒提,比如如何寫自己的線程池,除去SpringBoot的異步方式,Java8也給我們提供了異步編程方式CompletableFuture,這個如何使用呢?和SpringBoot提供的注解有何區(qū)別?
到此這篇關(guān)于詳解SpringBoot如何開啟異步編程的文章就介紹到這了,更多相關(guān)SpringBoot異步編程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?MVC概念+項目創(chuàng)建+@RequestMappring案例代碼
Spring?MVC?是?Spring?提供的一個基于?MVC?設(shè)計模式的輕量級?Web?開發(fā)框架,本質(zhì)上相當(dāng)于?Servlet,這篇文章主要介紹了Spring?MVC概念+項目創(chuàng)建+@RequestMappring,需要的朋友可以參考下2023-02-02在Android系統(tǒng)中使用WebViewClient處理跳轉(zhuǎn)URL的方法
這篇文章主要介紹了在Android系統(tǒng)中使用WebViewClient處理跳轉(zhuǎn)URL的方法,實現(xiàn)代碼為Java語言編寫,是需要的朋友可以參考下2015-07-07