現在的位置: 首頁 > 綜合 > 正文

什麼是SpringMvc非同步處理

2020年02月21日 綜合 ⁄ 共 3437字 ⁄ 字型大小 評論關閉

  Web容器對非同步的支持

  SpringMvc的實質就是對Java Web的封裝,因此為了更容易的理解SpringMvc的非同步,必須先了解Java Web的非同步。

  Java Web的實質就是一套標準(介面),Web容器實現了這套標準,所以我們站在Web容器的立場來說Java Web的非同步,非常好理解。

  無論採用什麼框架開發的Java Web應用,最終都是在Web容器中運行的,如tomcat就是最常用的Web容器。

  啟動SpringBoot時,可以看看日誌中列印出的線程名稱,其實就是tomcat線程池裡的線程。可以多請求幾次,發現每次處理請求的線程Id都不一樣。

  我們知道,其實每一個請求過來後,Web容器都會從自己的線程池中拿出一個線程來運行Java Web應用以處理請求。當請求處理完後,這個線程會被還回到線程池中以便處理下一個請求。

  如果請求能在非常短的時間內處理完成,是沒有問題的。我們設想一下,如果請求執行的非常慢,那麼這個線程將無法還回去,於是線程池中可用的線程數目將少一個。

  由於線程池的容量是有限的,如果很多執行的很慢的請求同時到來,那麼線程池中的線程將會用光,而且短時間內都還不回來。此時的其它請求都將無法被處理。

  我們發現執行很快的請求和非常耗時的請求是屬於兩種不同性質的事物,現在它們都由Web容器的線程池的線程來從頭處理到尾,就像當初的小王。

  既要接待客戶做諮詢工作,又要等客戶走了自己幹活。這顯然是低效的,唯一的方法就是將這兩種工作分開,分別交由兩個崗位去做。

  於是我們就看到,小王招了很多工人,自己專業做諮詢接活兒,轉手把活兒交給工人去做,工人做好後把活兒再交回給小王,小王再向業主交付。

  我們可以把這個思路套到Web容器上,就是Web容器線程池的線程只用來處理執行速度很快的請求,這對應於小王只負責諮詢接活兒。

  對於非常耗時的請求,Web容器線程池的線程將把這個請求交給其它專門的線程池的線程去處理,這對應於小王把接到的活兒交給他的工人去做。

  Web容器線程池的線程將再去接受其它請求,因為耗時的請求已經被交出去了。這對應於小王再去接別的活兒,因為上一個活兒已經交給工人去幹了。

  專門的線程池的線程處理完這個請求後會把它還回給Web容器線程池的線程,這對應於工人幹完活兒後會把活兒先交給小王去驗收。

  Web容器線程池的線程拿到處理結果,把結果寫入響應,這個請求就被處理完畢了。這對應於小王把工人干好的活兒交付給業主,施工就算完畢了。

  我們來分析一下,小王由於角色轉變而帶來的工作轉變都有哪些。一開始小王單打獨鬥,小王工作有三種,小王從業主接活兒,小王自己幹活,小王向業主交付。

  到最後小王成了工長,此時的工作是四種,小王從業主接活兒,小王把活兒交給工人去做,工人把做好的活兒交回給小王,小王向業主交付。

  對比後發現,小王為了使自己不幹活,所以引入了工人,以及由此產生的他與工人之間的一去一來的協調交互。

  同樣來分析一下,Web容器線程池的線程前前後後有哪些變化。一開始線程處理所有的請求,可以分為三個階段,線程接受請求,線程處理請求,線程寫迴響應。

  到最後線程的工作分為四個階段,線程接受請求,線程把待處理的請求交給專門的線程池,專門的線程池把處理好的請求交回給線程,線程寫迴響應。

  對比後發現,Web容器線程池的線程為了使自己不處理耗時請求,所以引入了專門的線程池,以及由此產生的它與專門的線程池的一去一來的協調交互。

  是不是發現線程池和小王的行為是完全對應的。而且他們面臨的問題都一樣,就是把需要處理的任務交出去給別人,別人把處理完畢的任務再還回給他。

  好了,現在我來告訴你,這就是Java Web非同步處理的整體邏輯思想。化繁為簡後,其實就是一去一來這兩個動作罷了。

  自己把執行流程交出去:

  1AsyncContext startAsync()

  別人把執行流程還回來:

  1void dispatch()

  上面這兩個方法就完成了一去一來這兩個動作,僅此而已。

  看到這些,可能有些人大失所望,傳說中「牛X和高大上」的非同步處理,到最後也不過區區十四個字,「執行流程交出去,執行流程還回來」。

  可能還會有人覺得,非同步處理不應該和客戶端也有關係嗎?其實並沒有,純粹是伺服器端的把戲。而且對於單個請求的處理時間也不會減少。

  就像你去飯店吃飯,你點的這份飯在後廚是一個廚師完成的還是多個廚師協作完成的,其實你並不知道,一般情況下你也並不關心。

  SpringMvc對非同步的支持

  上面描述的非同步處理邏輯只是一個規範(介面),是不帶實現的。任何想要支持Java Web非同步處理的,需要在遵守這個規範的前提下,自己提供一套實現。

  說白了,就是也要採用交出去還回來的模式,但是如何交出去,怎樣還回來,要自己去實現,還有這個專門的線程池也要自己來提供。

  SpringMvc提供了對非同步的支持,所以它遵守了這個規範,而且它也定義了相似的介面來與Java Web規範交互。

  這個介面就是AsyncWebRequest,它的實現類是StandardServletAsyncWebRequest。同樣的兩個方法。

  交出去:

  1void startAsync()

  還回來:

  1void dispatch()

  在這兩個方法的實現中,分別去調用了Java Web規範中的對應方法,這就是與Java Web規範的交互。

  同時SpringMvc自己是有線程池的,所以在第一個方法里實現了把執行流程交出去的操作,在第二個方法里實現了把執行流程還回來的操作。

  還有一個問題就是,並不是所有的方法都需要非同步處理,所以就需要有一種方式來告訴SpringMvc,哪個方法需要非同步處理。

  SpringMvc選擇了採用方法返回值的類型來進行區分,凡是@RequestMapping方法的返回值類型是以下這些的,就表明開發人員想進行非同步處理,於是SpringMvc就進行非同步處理。

  1Callable< V> 2 3DeferredResult< T> 4 5WebAsyncTask< V> 6 7StreamingResponseBody 8 9ResponseEntity< StreamingResponseBody>1011ResponseBodyEmitter1213ResponseEntity< ResponseBodyEmitter>

  對於非同步處理的方式,其實也包括兩大類,一類是開發人員不管,完全交給SpringMvc去處理,一類是開發人員自己把控,不用SpringMvc參與。

  對於第一類,我們返回Callable< V>類型就可以了,這樣SpringMvc會把它提交到線程池裡去執行。

  對於第二類,我們返回DeferredResult< T>類型即可,它的意思是延遲結果,就是需要延遲一會才會有結果,但是如何延遲呢,SpringMvc並不會去管。

  因此是由開發人員來決定的,開發人員需要自己弄個線程去執行,並且一定要記住最後要設置一下結果才行。

  我們看到耗時的代碼都跑到別的線程里去執行了,那麼SpringMvc的處理主流程自然就結束了,這就是執行流程交出去的過程。

  只不過有一點需要注意,這個請求的處理並沒有完成,只是暫時離開了SpringMvc的主流程,在別的線程池裡運行著呢。

  那麼一段時間後,別的線程池運行結束,已經取得了結果,那這個結果和執行流程又該如何還回來呢?

  直接還回來嗎?顯然是不可能的,因為SpringMvc的處理主流程在把任務交出去的那一刻就已經結束了、不存在了。

  其實是別的線程池在處理結束並得到結果後,處理流程連同結果會返回到Web容器中,由Web容器再次分派這個請求到SpringMvc中來。

  這就是為什麼上面第二個方法的名字叫做dispatch的原因,就是再次分派這個請求到Web應用中來嘛,因此會再次進入到SpringMvc中,這不就把執行流程還回來了嘛。

  可能會有人產生疑惑,如果SpringMvc再次處理這個請求那不就亂套了嗎?答案是顯然不會的,因為這次非同步的結果已經存在了,自然不會再非同步處理了,而是把它跳過去直接進入後續處理。

  也就是對方法返回值(ReturnValue)的處理,比如序列化成JSON寫入響應,或進行視圖渲染,把渲染後的視圖寫入響應,這樣這個請求就算處理完畢了。

抱歉!評論已關閉.