Java SpringMVC異步處理詳解
1、本篇內容
本文讓大傢掌握 springmvc 中異步處理請求,特別牛逼的一個功能,大傢一定要掌握。
2、看段代碼,分析問題
@ResponseBody @RequestMapping("/async/m1.do") public String m1() throws InterruptedException { long st = System.currentTimeMillis(); System.out.println("主線程:" + Thread.currentThread() + "," + st + ",開始"); //休眠3秒,模擬耗時的業務操作 TimeUnit.SECONDS.sleep(3); long et = System.currentTimeMillis(); System.out.println("主線程:" + Thread.currentThread() + "," + st + ",結束,耗時(ms):" + (et - st)); return "ok"; }
這段代碼很簡單
這段代碼是 springmvc 提供的一個接口
內部休眠瞭 3 秒鐘,用來模擬耗時的操作
方法內部有 2 條日志(日志中包含瞭當前線程、開始時間、結束時間、耗時)
瀏覽器中訪問下這個接口,效果如下,可以看到接口耗時 3s 左右。
控制臺輸出
主線程:Thread[http-nio-8080-exec-1,5,main],1624889293055,開始 主線程:Thread[http-nio-8080-exec-1,5,main],1624889293055,結束,耗時(ms):3002
從輸出中,我們可以看出,這個接口從開始到結束都是由 tomcat 中的線程來處理用戶請求的,也就是說,3 秒這段時間內,tomcat 中的一個線程會被當前請求一直占用瞭則,tomcat 線程是有最大值的,默認情況下好像是 75,那麼問題來瞭。
當 3 秒之內,來的請求數量超過瞭 tomcat 最大線程數的時候,其他請求就無法處理瞭,而此時 tomcat 中這些線程都處理 sleep 3s 的休眠狀態,cpu 此時沒活幹,此時就會造成機器沒活幹,但是呢又不能處理新的請求,這就是坑啊,浪費資源,怎麼辦呢?
遇到這種場景的,也就是說接口內部比價耗時,但是又不能充分利用 cpu 的,我們可以采用異步的方式來處理請求,過程如下:
tomcat 線程,將請求轉發給我們自定義的子線程去處理這個請求,然後 tomcat 就可以繼續去接受新的請求瞭。
3、springmvc 中異步處理
主要有 3 個大的步驟。
step1:servlet 開啟異步處理支持
web.xml 中開啟 servlet 異步支持
step2:Filter 中添加異步支持
如果我們的異步請求需要經過 Filter 的,那麼需要在 web.xml 對這個 Filter 添加異步支持.
step3:接口返回值為 DeferredResult
這個步驟中細節比較多,當需要異步響應請求的時候,返回值需要為 DeferredResult,具體參考下面案例代碼,詳細信息都在註釋中瞭,大傢註意看註釋。
第 1 步:創建 DeferredResult<返回值類型>(超時時間[毫秒],超時回調的代碼)
第 2 步:在子線程中異步處理業務,調用 DeferredResult 的 setResult 方法,設置最終返回到客戶端的結果,此方法調用以後,客戶端將接收到返回值,然後響應過程請求就結束瞭
第 3 步:將 DefaultResult 作為方法返回值
/** * 使用springmvc的異步功能,業務處理放在異步線程中執行 * * @param timeout 異步處理超時時間(毫秒) * @return */ @ResponseBody @RequestMapping("/async/m2/{timeout}.do") public DeferredResult m2(@PathVariable("timeout") long timeout) { long st = System.currentTimeMillis(); System.out.println("主線程:" + Thread.currentThread() + "," + st + ",開始"); /** * 1、創建DeferredResult<返回值類型>(超時時間[毫秒],超時回調的代碼) */ DeferredResult result = new DeferredResult(timeout, () -> { System.out.println("超時瞭"); return "timeout"; }); //2、異步處理業務, new Thread(() -> { //開啟一個異步線程,在異步線程中進行業務處理操作 try { TimeUnit.SECONDS.sleep(3); //3、調用DeferredResult的setResult方法,設置最終返回到客戶端的結果,此方法調用以後,客戶端將接收到返回值 result.setResult("ok"); } catch (InterruptedException e) { result.setResult("發生異常瞭:" + e.getMessage()); } }).start(); long et = System.currentTimeMillis(); System.out.println("主線程:" + Thread.currentThread() + "," + st + ",結束,耗時(ms):" + (et - st)); //3、將DefaultResult作為方法返回值 return result; }
上面的 m2 方法個 timeout 參數,調用者通過這個參數來指定接口的超時時間,未超時的情況下,也就是說 timeout 大於 3 秒的時候,此時會輸出 ok,否則將出現超時,此時會將 DeferredResult 構造器第 2 個參數的執行結果作為最終的響應結果,即會向客戶端輸出 timeout。
使用建議:案例開啟瞭一個新的子線程來執行業務操作,生產環境中,建議大傢采用線程池的方式,效率更高。
下面我們來通過 2 個 case 來模擬下這個接口超時和正常的結果。
4、模擬非超時請求
當 timeout 大於 3 秒時,才不會出現超時,此時我們傳遞 4000 毫秒來試試
控制臺輸出如下,可以看到主線程瞬間就結束瞭。
主線程:Thread[http-nio-8080-exec-6,5,main],1624891886020,開始 主線程:Thread[http-nio-8080-exec-6,5,main],1624891886020,結束,耗時(ms):0
5、模擬超時請求
當 timeout 小於 3 秒會出現超時,此時我們傳遞 1000 毫秒來試試
控制臺輸出如下,輸出瞭超時信息,且通過前兩行輸出看出主線程瞬間就結束瞭,不會被請求阻塞。
主線程:Thread[http-nio-8080-exec-1,5,main],1624892109695,開始 主線程:Thread[http-nio-8080-exec-1,5,main],1624892109695,結束,耗時(ms):0 超時瞭
6、總結
當接口中有大量的耗時的操作,且這些耗時的操作讓線程處於等待狀態時,此時為瞭提升系統的性能,可以將接口調整為異步處理的方式。
本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!
推薦閱讀:
- Spring中註解方式的異步請求
- springMvc中DeferredResult的long polling應用示例解析
- 詳解Spring DeferredResult異步操作使用場景
- Spring 異步接口返回結果的四種方式
- Spring中的使用@Async異步調用方法