詳解SpringBoot如何開(kāi)啟異步編程
一、什么是異步?
現(xiàn)在我們假設(shè)有一個(gè)接口方法,里面又調(diào)用了三個(gè)子方法,分別是A,B,C。先從A執(zhí)行,執(zhí)行完畢再執(zhí)行B,B執(zhí)行完最后執(zhí)行C。這也是我們代碼最常見(jiàn)的執(zhí)行方式。ABC順序執(zhí)行,其中一個(gè)出問(wèn)題了,如果拋出了異常,后續(xù)則不再執(zhí)行。這中方式就是同步執(zhí)行。
那么異步執(zhí)行是什么樣子的呢?
假設(shè)B方法改為異步,那么A方法執(zhí)行完畢,執(zhí)行B方法。此時(shí)不需要等B方法執(zhí)行完畢,代碼會(huì)直接執(zhí)行C方法。也就是B方法不再影響C方法的執(zhí)行,這里B方法就是異步執(zhí)行。
二、為什么要使用異步編程?
異步方法的作用也很明顯,假設(shè)我們上面的接口方法是一個(gè)用戶注冊(cè)方法,A方法注冊(cè)成功,B方法是增加積分,C方法增加權(quán)限。那么A方法執(zhí)行成功后,我們就可以給用戶添加權(quán)限了。至于增加積分,完全可以異步處理,這樣注冊(cè)的效率就會(huì)更高,提高用戶體驗(yàn)。實(shí)際上的業(yè)務(wù)場(chǎng)景還有很多,通常需要異步的都是執(zhí)行比較慢,又對(duì)我們串行執(zhí)行的業(yè)務(wù)邏輯沒(méi)有影響,為了提高代碼效率,我們就需要異步執(zhí)行。
三、SpringBoot開(kāi)啟異步編程
接下來(lái),我們就以SpringBoot項(xiàng)目為例,來(lái)看下如何使用異步編程。
第一步,通常來(lái)說(shuō)一個(gè)新功能都是從引入包開(kāi)始,因?yàn)檫@個(gè)功能本身是Spring 3提供的功能,所以我們可以直接使用。首先在主啟動(dòng)類(lèi)上通過(guò)注解@EnableAsync開(kāi)啟異步功能。
@SpringBootApplication
@EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}第二步,在需要異步的方法上,添加注解@Async,這樣就可以了。
@Async
public void testAsync() {
xxxx;
}
是不是非常簡(jiǎn)單?使用當(dāng)然非常簡(jiǎn)單了,但是這里面有許多坑和業(yè)務(wù)場(chǎng)景,我們就來(lái)一一說(shuō)明。
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;
};
}我們?cè)贑ontroller層的testAsync接口中,分別調(diào)用了service層中的testA,testB,testC方法。其中testB我們加了@Async異步注解,那么在主線程testAsync中執(zhí)行testB就無(wú)需等待,會(huì)繼續(xù)執(zhí)行testC方法。
如果是沒(méi)有返回值的testB方法,那么這樣就可以滿足我們的需求了,但是如果testB有返回值呢,而且需要我們對(duì)返回值進(jìn)行存儲(chǔ)。那么就需要有返回值的異步方法。這個(gè)也比較簡(jiǎn)單,我們只需要使用Future(java.util類(lèi)中的)來(lái)進(jìn)行返回。
@RestController
public class TestController {
public Result testAsync(){
service.testA();
Future future = service.testB();
//testC不許等待testB執(zhí)行完畢,即可執(zhí)行
service.testC();
//get()會(huì)一直等待有結(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類(lèi)型的返回。此時(shí)就可以獲得異步方法的返回了,但是注意我們這里是異步,所以在使用返回結(jié)果future時(shí),可能是沒(méi)有值的,因?yàn)槲覀儺惒椒椒赡軟](méi)有執(zhí)行完畢。這里需要我們使用future.get()方法獲取返回結(jié)果,get方法會(huì)自旋一直等待,直到有返回結(jié)果才繼續(xù)往下執(zhí)行。
這里使用get獲取結(jié)果時(shí)要注意了,如果放在testC前面獲取異步結(jié)果,就會(huì)導(dǎo)致testC無(wú)法串行執(zhí)行,所以一定要根據(jù)業(yè)務(wù)場(chǎng)景來(lái)使用。比如我們將獲取testB的結(jié)果放在了最后一行,testC串行執(zhí)行,在最終返回結(jié)果前再獲取異步testB方法的返回結(jié)果。
2、@Async使用的線程池
在使用@Async注解時(shí),你一定從網(wǎng)上看到過(guò)許多人說(shuō),不要使用默認(rèn)線程池,這個(gè)是怎么回事呢?
首先,我們要明白,@Async的異步實(shí)現(xiàn),其實(shí)就是起一個(gè)新的線程,和主線程區(qū)分開(kāi),來(lái)執(zhí)行異步的方法。這樣你主線程繼續(xù)串行執(zhí)行,我新起的子線程執(zhí)行你的異步方法,互不影響,這樣就實(shí)現(xiàn)了異步執(zhí)行的目的。
牽扯到多線程,那就一定離不開(kāi)線程池的使用,這個(gè)在這里不詳細(xì)說(shuō),我會(huì)在線程池相關(guān)的文章里說(shuō)明?;氐絼偛诺膯?wèn)題,網(wǎng)上許多人說(shuō)的,不要使用默認(rèn)線程池,是因?yàn)锧Async以前版本使用的默認(rèn)線程池是SimpleAsyncTaskExecutor,這個(gè)線程池有許多問(wèn)題,推薦使用的都是ThreadPoolTaskExecutor。
實(shí)際上,SpringBoot已經(jīng)考慮到這個(gè)問(wèn)題了,在新的版本中@Async默認(rèn)使用的線程池就是ThreadPoolTaskExecutor,這里注意看下你使用的版本。
SpringBoot 2.0.9以及之前的版本,使用的線程池默認(rèn)是SimpleAsyncTaskExecutor,之后默認(rèn)使用的是ThreadPoolTaskExecutor
3、SpringBoot使用的默認(rèn)線程池源碼解析
SpringBoot默認(rèn)使用的是哪個(gè)線程池,是如何配置的呢?這一部分,感興趣的可以看下,不感興趣的直接看下一節(jié),配置線程池參數(shù)就可以了。
找到@Async的攔截器方法,每次會(huì)先執(zhí)行。

入?yún)⒌腅xcutor參數(shù)可以是null,如果不是null,就使用指定的Excutor,這個(gè)就可以來(lái)指定我們自己的Excutor,這個(gè)后面說(shuō)。

如果入?yún)⑹莕ull,就使用如下方法獲取。這里有三行關(guān)鍵代碼,第一行通過(guò)beanFactory.getBean獲取TaskExcutor類(lèi)型的Bean。但是這里是通過(guò)類(lèi)型獲取的,實(shí)現(xiàn)Excutor的如果有多個(gè)類(lèi),就會(huì)報(bào)錯(cuò),被第二行代碼捕獲,進(jìn)入第三行代碼獲取Bean。第三行通過(guò)名字獲取的Bean,名字是taskExcutor。


名字是taskExcutor的Bean,是SpringBoot默認(rèn)給我們配置好了的,接著看SpringBoot的自動(dòng)配置相關(guān)源碼TaskExecutionAutoConfiguration類(lèi),紅框里最終配置的Bean名字就是taskExcutor。


這里注意這個(gè)注解@ConditionalOnMissingBean,只在沒(méi)有Excutor實(shí)現(xiàn)類(lèi)的時(shí)候,才會(huì)默認(rèn)配置。和上面通過(guò)beanFactory.getBean獲取TaskExcutor類(lèi)型的Bean代碼呼應(yīng)起來(lái)了。如果我們項(xiàng)目沒(méi)有注入Excutor的話,SpringBoot就會(huì)默認(rèn)注入ThreadPoolTaskExecutor。如果我們指定了,這樣使用@Async(“myExcutor”)則使用我們指定的。如果沒(méi)指定,還有多個(gè)實(shí)現(xiàn)Bean沖突了,則使用Bean名字是taskExcutor的。

我們?cè)賮?lái)看下默認(rèn)線程池配置,創(chuàng)建線程池時(shí)使用的都是TaskExecutionProperties這個(gè)配置文件。

我們?cè)賮?lái)看這個(gè)配置文件,紅框處是幾個(gè)關(guān)鍵參數(shù)。

問(wèn)題非常明顯,有幾個(gè)參數(shù)值最大值非常大,Integer.MAX_VALUE。需要我們?cè)谑褂脮r(shí)根據(jù)業(yè)務(wù)場(chǎng)景進(jìn)行配置,不然存在隱患。
4、線程池配置
首先,我們需要了解線程池的基本運(yùn)行原理,才能更好的配置。這里簡(jiǎn)單給大家介紹一下,在后面專門(mén)的線程池文章會(huì)詳細(xì)說(shuō)明。
線程池有幾個(gè)重要的基本參數(shù),核心線程數(shù),等待隊(duì)列,最大線程數(shù)量,線程存活時(shí)間等。有新的任務(wù)過(guò)來(lái)時(shí),先啟用核心線程,核心線程滿了后,再加到等待隊(duì)列,等待隊(duì)列也滿了后,如果小于最大線程數(shù),則繼續(xù)開(kāi)啟新的線程,直到達(dá)到最大線程數(shù)量。
所以,上文線程池默認(rèn)配置中,系統(tǒng)默認(rèn)配置的核心線程數(shù)是8,但是等待隊(duì)列最大值無(wú)上限,這里就不太合適。需要按照業(yè)務(wù)實(shí)際使用場(chǎng)景來(lái)配置。同時(shí)最大線程數(shù)也需要配置,不然資源不夠,起了無(wú)限多的線程,就OOM了。
存活時(shí)間默認(rèn)60s,也是根據(jù)自己的任務(wù)響應(yīng)時(shí)間來(lái)配置。allowCoreThreadTimeout是配置核心線程數(shù)是否設(shè)置存活時(shí)間的參數(shù),如果配置true,那么核心線程數(shù)存活時(shí)間也會(huì)生效,空閑時(shí)不會(huì)一直存在。
配置時(shí)可以自己通過(guò)代碼設(shè)置,也可以直接在配置文件設(shè)置,下面是簡(jiǎn)單參考。
spring:
task:
execution:
pool:
core-size: 8
queue-capacity: 100
max-size: 16
keep-alive: 60
allow-core-thread-timeout: false四、結(jié)語(yǔ)
到這,我們今天的文章就算結(jié)束了,大家跟著文章,應(yīng)該學(xué)會(huì)使用異步編程了,能夠滿足大多數(shù)場(chǎng)景的使用了。
當(dāng)然,還有些許多問(wèn)題因?yàn)槠鶈?wèn)題這里沒(méi)提,比如如何寫(xiě)自己的線程池,除去SpringBoot的異步方式,Java8也給我們提供了異步編程方式CompletableFuture,這個(gè)如何使用呢?和SpringBoot提供的注解有何區(qū)別?
到此這篇關(guān)于詳解SpringBoot如何開(kāi)啟異步編程的文章就介紹到這了,更多相關(guān)SpringBoot異步編程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Java中AC自動(dòng)機(jī)的原理與實(shí)現(xiàn)
AC自動(dòng)機(jī)是一個(gè)多模式匹配算法,在模式匹配領(lǐng)域被廣泛應(yīng)用。本文將詳細(xì)為大家介紹AC自動(dòng)機(jī)的原理與實(shí)現(xiàn)方法,感興趣的可以了解一下2022-05-05
通過(guò)實(shí)例解析POJO和JavaBean的區(qū)別
這篇文章主要介紹了通過(guò)實(shí)例解析POJO和JavaBean的區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07
Spring?MVC概念+項(xiàng)目創(chuàng)建+@RequestMappring案例代碼
Spring?MVC?是?Spring?提供的一個(gè)基于?MVC?設(shè)計(jì)模式的輕量級(jí)?Web?開(kāi)發(fā)框架,本質(zhì)上相當(dāng)于?Servlet,這篇文章主要介紹了Spring?MVC概念+項(xiàng)目創(chuàng)建+@RequestMappring,需要的朋友可以參考下2023-02-02
Java SpringBoot的相關(guān)知識(shí)點(diǎn)詳解
這篇文章主要介紹了SpringBoot的相關(guān)知識(shí)點(diǎn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2021-10-10
在Android系統(tǒng)中使用WebViewClient處理跳轉(zhuǎn)URL的方法
這篇文章主要介紹了在Android系統(tǒng)中使用WebViewClient處理跳轉(zhuǎn)URL的方法,實(shí)現(xiàn)代碼為Java語(yǔ)言編寫(xiě),是需要的朋友可以參考下2015-07-07

