5種SpringMvc的異步處理方式你都了解嗎?
引言
說(shuō)到異步大家肯定首先會(huì)先想到同步。我們先來(lái)看看什么是同步?所謂同步,就是發(fā)出一個(gè)功能調(diào)用時(shí),在沒(méi)有得到結(jié)果之前,該調(diào)用就不返回或繼續(xù)執(zhí)行后續(xù)操作。簡(jiǎn)單來(lái)說(shuō),同步就是必須一件一件事做,等前一件做完了才能做下一件事。異步:異步就相反,調(diào)用在發(fā)出之后,這個(gè)調(diào)用就直接返回了,不需要等結(jié)果。
瀏覽器同步
瀏覽器發(fā)起一個(gè)request然后會(huì)一直待一個(gè)響應(yīng)response,在這期間里面它是阻塞的。 比如早期我們?cè)谖覀冊(cè)诠潆娚唐脚_(tái)的時(shí)候買東西我們打開(kāi)一個(gè)商品的頁(yè)面,大致流程是不是可能是這樣,每次打開(kāi)一個(gè)頁(yè)面都是由一個(gè)線程從頭到尾來(lái)處理,這個(gè)請(qǐng)求需要進(jìn)行數(shù)據(jù)庫(kù)的訪問(wèn)需要把商品價(jià)格庫(kù)存啥的返回頁(yè)面,還需要去調(diào)用第三方接口,比如優(yōu)惠券接口等我們只有等到這些都處理完成后這個(gè)線程才會(huì)把結(jié)果響應(yīng)給瀏覽器,在這等結(jié)果期間這個(gè)線程只能一直在干等著啥事情也不能干。這樣的話是不是會(huì)有有一定的性能問(wèn)題。大致的流程如下:
瀏覽器異步
為了解決上面同步阻塞的問(wèn)題,再Servlet3.0發(fā)布后,提供了一個(gè)新特性:異步處理請(qǐng)求。比如我們還是進(jìn)入商品詳情頁(yè)面,這時(shí)候這個(gè)前端發(fā)起一個(gè)請(qǐng)求,然后會(huì)有一個(gè)線程來(lái)執(zhí)行這個(gè)請(qǐng)求,這個(gè)請(qǐng)求需要去數(shù)據(jù)庫(kù)查詢庫(kù)存、調(diào)用第三方接口查詢優(yōu)惠券等。這時(shí)候這個(gè)線程就不用干等著呢。它的任務(wù)到這就完成了,又可以執(zhí)行下一個(gè)任務(wù)了。等查詢數(shù)據(jù)庫(kù)和第三方接口查詢優(yōu)惠券有結(jié)果了,這時(shí)候會(huì)有一個(gè)新的線程來(lái)把處理結(jié)果返回給前端。這樣的話線程的工作量是不超級(jí)飽和,需要不停的干活,連休息的機(jī)會(huì)都不給了。

這個(gè)異步是純后端的異步,對(duì)前端是無(wú)感的,異步也并不會(huì)帶來(lái)響應(yīng)時(shí)間上的優(yōu)化,原來(lái)該執(zhí)行多久照樣還是需要執(zhí)行多久。但是我們的請(qǐng)求線程(Tomcat 線程)為異步servlet之后,我們可以立即返回,依賴于業(yè)務(wù)的任務(wù)用業(yè)務(wù)線程來(lái)執(zhí)行,也就是說(shuō),Tomcat的線程可以立即回收,默認(rèn)情況下,Tomcat的核心線程是10,最大線程數(shù)是200,我們能及時(shí)回收線程,也就意味著我們能處理更多的請(qǐng)求,能夠增加我們的吞吐量,這也是異步Servlet的主要作用。下面我們就來(lái)看看Spring mvc 的幾種異步方式吧 https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-async
在這個(gè)之前我們還是先簡(jiǎn)單的回顧下Servlet 3.1的異步:客戶端(瀏覽器、app)發(fā)送一個(gè)請(qǐng)求 Servlet容器分配一個(gè)線程來(lái)處理容器中的一個(gè)servlet servlet調(diào)用request.startAsync()開(kāi)啟異步模式,保存AsyncContext, 然后返回。 這個(gè)servlet請(qǐng)求線程以及所有的過(guò)濾器都可以結(jié)束,但其響應(yīng)(response)會(huì)等待異步線程處理結(jié)束后再返回。 其他線程使用保存的AsyncContext來(lái)完成響應(yīng) 客戶端收到響應(yīng) 
Callable
?/**??公眾號(hào):java金融
?????*?使用Callable
?????*?@return
?????*/
????@GetMapping("callable")
????public?Callable?callable()? {
????????System.out.println(LocalDateTime.now().toString()?+?"--->主線程開(kāi)始");
????????Callable?callable?=?()?->?{
????????????String?result?=?"return?callable";
????????????//?執(zhí)行業(yè)務(wù)耗時(shí)?5s
????????????Thread.sleep(5000);
????????????System.out.println(LocalDateTime.now().toString()?+?"--->子任務(wù)線程("+Thread.currentThread().getName()+")");
????????????return?result;
????????};
????????System.out.println(LocalDateTime.now().toString()?+?"--->主線程結(jié)束");
????????return?callable;
????}
???????public?static?String?doBusiness()?{
????????//?執(zhí)行業(yè)務(wù)耗時(shí)?10s
????????try?{
????????????Thread.sleep(10000);
????????}?catch?(InterruptedException?e)?{
????????????e.printStackTrace();
????????}
????????return?UUID.randomUUID().toString();
????}
控制器先返回一個(gè)Callable對(duì)象 Spring MVC開(kāi)始進(jìn)行異步處理,并把該Callable對(duì)象提交給另一個(gè)獨(dú)立線程的執(zhí)行器TaskExecutor處理 DispatcherServlet和所有過(guò)濾器都退出Servlet容器線程,但此時(shí)方法的響應(yīng)對(duì)象仍未返回 Callable對(duì)象最終產(chǎn)生一個(gè)返回結(jié)果,此時(shí)Spring MVC會(huì)重新把請(qǐng)求分派回Servlet容器,恢復(fù)處理 DispatcherServlet再次被調(diào)用,恢復(fù)對(duì)Callable異步處理所返回結(jié)果的處理 上面就是Callable的一個(gè)執(zhí)行流程,下面我們來(lái)簡(jiǎn)單的分析下源碼,看看是怎么實(shí)現(xiàn)的:我們知道SpringMvc是可以返回json格式數(shù)據(jù)、或者返回視圖頁(yè)面(html、jsp)等,SpringMvc是怎么實(shí)現(xiàn)這個(gè)的呢?最主要的一個(gè)核心類就是org.springframework.web.method.support.HandlerMethodReturnValueHandler 我們來(lái)看看這個(gè)類,這個(gè)類就是一個(gè)接口,總共就兩個(gè)方法;
boolean?supportsReturnType(MethodParameter?returnType);
void?handleReturnValue(@Nullable?Object?returnValue,?MethodParameter?returnType,ModelAndViewContainer?mavContainer,?NativeWebRequest?webRequest)?throws?Exception;
上面這個(gè)我們的請(qǐng)求是返回Callable
開(kāi)啟異步線程的話也就是在handleReturnValue這個(gè)方法里面了,感興趣的大家可以動(dòng)手去debug下還是比較好調(diào)試的。
CompletableFuture ?和ListenableFuture
???@GetMapping("completableFuture")
????public?CompletableFuture?completableFuture()? {
????????//?線程池一般不會(huì)放在這里,會(huì)使用static聲明,這只是演示
????????ExecutorService?executor?=?Executors.newCachedThreadPool();
????????System.out.println(LocalDateTime.now().toString()?+?"--->主線程開(kāi)始");
????????CompletableFuture?completableFuture?=?CompletableFuture.supplyAsync(IndexController::doBusiness,?executor);
????????System.out.println(LocalDateTime.now().toString()?+?"--->主線程結(jié)束");
????????return?completableFuture;
????}
????@GetMapping("listenableFuture")
????public?ListenableFuture?listenableFuture()? {
????????//?線程池一般不會(huì)放在這里,會(huì)使用static聲明,這只是演示
????????ExecutorService?executor?=?Executors.newCachedThreadPool();
????????System.out.println(LocalDateTime.now().toString()?+?"--->主線程開(kāi)始");
????????ListenableFutureTask?listenableFuture?=?new?ListenableFutureTask<>(()->???doBusiness());
????????executor.execute(listenableFuture);
????????System.out.println(LocalDateTime.now().toString()?+?"--->主線程結(jié)束");
????????return?listenableFuture;
????}
注:這種方式記得不要使用內(nèi)置的不要使用內(nèi)置的 ForkJoinPool線程池,需要自己創(chuàng)建線程池否則會(huì)有性能問(wèn)題
WebAsyncTask
?@GetMapping("asynctask")
????public?WebAsyncTask?asyncTask()?{
????????SimpleAsyncTaskExecutor?executor?=?new?SimpleAsyncTaskExecutor();
????????System.out.println(LocalDateTime.now().toString()?+?"--->主線程開(kāi)始");
????????WebAsyncTask?task?=?new?WebAsyncTask(1000L,?executor,?()->?doBusiness());
????????task.onCompletion(()->{
????????????System.out.println(LocalDateTime.now().toString()?+?"--->調(diào)用完成");
????????});
????????task.onTimeout(()->{
????????????System.out.println("onTimeout");
????????????return?"onTimeout";
????????});
????????System.out.println(LocalDateTime.now().toString()?+?"--->主線程結(jié)束");
????????return?task;
????}
DeferredResult
????@GetMapping("deferredResult")
????public?DeferredResult?deferredResult()? {
????????System.out.println(LocalDateTime.now().toString()?+?"--->主線程("+Thread.currentThread().getName()+")開(kāi)始");
????????DeferredResult?deferredResult?=?new?DeferredResult<>();
????????CompletableFuture.supplyAsync(()->?doBusiness(),?Executors.newFixedThreadPool(5)).whenCompleteAsync((result,?throwable)->{
????????????if?(throwable!=null)?{
????????????????deferredResult.setErrorResult(throwable.getMessage());
????????????}else?{
????????????????deferredResult.setResult(result);
????????????}
????????});
????????//?異步請(qǐng)求超時(shí)時(shí)調(diào)用
????????deferredResult.onTimeout(()->{
????????????System.out.println(LocalDateTime.now().toString()?+?"--->onTimeout");
????????});
????????//?異步請(qǐng)求完成后調(diào)用
????????deferredResult.onCompletion(()->{
????????????System.out.println(LocalDateTime.now().toString()?+?"--->onCompletion");
????????});
????????System.out.println(LocalDateTime.now().toString()?+?"--->主線程("+Thread.currentThread().getName()+")結(jié)束");
????????return?deferredResult;
????}
上面這幾種異步方式都是會(huì)等到業(yè)務(wù)doBusiness執(zhí)行完之后(10s)才會(huì)把response給到前端,執(zhí)行請(qǐng)求的主線程會(huì)立即結(jié)束,響應(yīng)結(jié)果會(huì)交給另外的線程來(lái)返回給前端。 這種異步跟下面的這個(gè)所謂的假異步是不同的,這種情況是由主線程執(zhí)行完成之后立馬返回值(主線程)給前端,不會(huì)等個(gè)5s在返回給前端。
????@GetMapping("call")
????public?String?call()?{
???????new?Thread(new?Runnable()?{
???????????@Override
???????????public?void?run()?{
???????????????try?{
???????????????????Thread.sleep(5000);
???????????????}?catch?(InterruptedException?e)?{
???????????????????e.printStackTrace();
???????????????}
???????????}
???????}).start();
????????return?"這是個(gè)假異步";
????}
這幾種異步方式都跟返回Callable 差不多,都有對(duì)應(yīng)的HandlerMethodReturnValueHandler 實(shí)現(xiàn)類,無(wú)非就是豐富了自己一些特殊的api、比如超時(shí)設(shè)置啥的,以及線程池的創(chuàng)建是誰(shuí)來(lái)創(chuàng)建,執(zhí)行流程基本都是一樣的。
總結(jié)
了解spring mvc 的異步編程,對(duì)我們后續(xù)學(xué)習(xí)響應(yīng)式編程、rxjava、webflux等都是有好處的。 異步編程可以幫我們高效的利用系統(tǒng)資源。
結(jié)束
由于自己才疏學(xué)淺,難免會(huì)有紕漏,假如你發(fā)現(xiàn)了錯(cuò)誤的地方,還望留言給我指出來(lái),我會(huì)對(duì)其加以修正。 如果你覺(jué)得文章還不錯(cuò),你的轉(zhuǎn)發(fā)、分享、贊賞、點(diǎn)贊、留言就是對(duì)我最大的鼓勵(lì)。 感謝您的閱讀,十分歡迎并感謝您的關(guān)注。
站在巨人的肩膀上摘蘋果: https://blog.csdn.net/f641385712/article/details/88692534
????????????????????????
最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊(cè)》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫(kù)、數(shù)據(jù)結(jié)構(gòu)、等等。
獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù) 666?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。
謝謝支持喲 (*^__^*)



