引言
說到非同步大家肯定首先會先想到同步。我們先來看看什麼是同步?所謂 同步 ,就是發出一個功能呼叫時,在沒有得到結果之前,該呼叫就不返回或繼續執行後續操作。簡單來說,同步就是必須一件一件事做,等前一件做完了才能做下一件事。 非同步 :非同步就相反,呼叫在發出之後,這個呼叫就直接返回了,不需要等結果。
瀏覽器同步瀏覽器發起一個request然後會一直待一個響應response,在這期間裡面它是阻塞的。比如早期我們在我們在逛電商平臺的時候買東西我們開啟一個商品的頁面,大致流程是不是可能是這樣,每次開啟一個頁面都是由一個執行緒從頭到尾來處理,這個請求需要進行資料庫的訪問需要把商品價格庫存啥的返回頁面,還需要去呼叫第三方介面,比如優惠券介面等我們只有等到這些都處理完成後這個執行緒才會把結果響應給瀏覽器,在這等結果期間這個執行緒只能一直在乾等著啥事情也不能幹。這樣的話是不是會有有一定的效能問題。大致的流程如下:
瀏覽器非同步為了解決上面同步阻塞的問題,再 Servlet3.0 釋出後,提供了一個新特性: 非同步處理請求 。比如我們還是進入商品詳情頁面,這時候這個前端發起一個請求,然後會有一個執行緒來執行這個請求,這個請求需要去資料庫查詢庫存、呼叫第三方介面查詢優惠券等。這時候這個執行緒就不用幹等著呢。它的任務到這就完成了,又可以執行下一個任務了。等查詢資料庫和第三方介面查詢優惠券有結果了,這時候會有一個新的執行緒來把處理結果返回給前端。這樣的話執行緒的工作量是不超級飽和,需要不停的幹活,連休息的機會都不給了。
在這裡插入圖片描述
開啟非同步執行緒的話也就是在handleReturnValue這個方法裡面了,感興趣的大家可以動手去debug下還是比較好除錯的。
CompletableFuture 和ListenableFuture @GetMapping("completableFuture") public CompletableFuture<String> completableFuture() { // 執行緒池一般不會放在這裡,會使用static宣告,這只是演示 ExecutorService executor = Executors.newCachedThreadPool(); System.out.println(LocalDateTime.now().toString() + "--->主執行緒開始"); CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(IndexController::doBusiness, executor); System.out.println(LocalDateTime.now().toString() + "--->主執行緒結束"); return completableFuture; } @GetMapping("listenableFuture") public ListenableFuture<String> listenableFuture() { // 執行緒池一般不會放在這裡,會使用static宣告,這只是演示 ExecutorService executor = Executors.newCachedThreadPool(); System.out.println(LocalDateTime.now().toString() + "--->主執行緒開始"); ListenableFutureTask<String> listenableFuture = new ListenableFutureTask<>(()-> doBusiness()); executor.execute(listenableFuture); System.out.println(LocalDateTime.now().toString() + "--->主執行緒結束"); return listenableFuture; }
注:這種方式記得不要使用內建的不要使用內建的 ForkJoinPool執行緒池,需要自己建立執行緒池否則會有效能問題WebAsyncTask @GetMapping("asynctask") public WebAsyncTask asyncTask() { SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor(); System.out.println(LocalDateTime.now().toString() + "--->主執行緒開始"); WebAsyncTask<String> task = new WebAsyncTask(1000L, executor, ()-> doBusiness()); task.onCompletion(()->{ System.out.println(LocalDateTime.now().toString() + "--->呼叫完成"); }); task.onTimeout(()->{ System.out.println("onTimeout"); return "onTimeout"; }); System.out.println(LocalDateTime.now().toString() + "--->主執行緒結束"); return task; }
DeferredResult @GetMapping("deferredResult") public DeferredResult<String> deferredResult() { System.out.println(LocalDateTime.now().toString() + "--->主執行緒("+Thread.currentThread().getName()+")開始"); DeferredResult<String> deferredResult = new DeferredResult<>(); CompletableFuture.supplyAsync(()-> doBusiness(), Executors.newFixedThreadPool(5)).whenCompleteAsync((result, throwable)->{ if (throwable!=null) { deferredResult.setErrorResult(throwable.getMessage()); }else { deferredResult.setResult(result); } }); // 非同步請求超時時呼叫 deferredResult.onTimeout(()->{ System.out.println(LocalDateTime.now().toString() + "--->onTimeout"); }); // 非同步請求完成後呼叫 deferredResult.onCompletion(()->{ System.out.println(LocalDateTime.now().toString() + "--->onCompletion"); }); System.out.println(LocalDateTime.now().toString() + "--->主執行緒("+Thread.currentThread().getName()+")結束"); return deferredResult; }
上面這幾種非同步方式都是會等到業務doBusiness執行完之後(10s)才會把response給到前端,執行請求的主執行緒會立即結束,響應結果會交給另外的執行緒來返回給前端。這種非同步跟下面的這個所謂的假非同步是不同的,這種情況是由主執行緒執行完成之後立馬返回值(主執行緒)給前端,不會等個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 "這是個假非同步"; }
這幾種非同步方式都跟返回Callable 差不多,都有對應的HandlerMethodReturnValueHandler 實現類,無非就是豐富了自己一些特殊的api、比如超時設定啥的,以及執行緒池的建立是誰來建立,執行流程基本都是一樣的。
總結瞭解spring mvc 的非同步程式設計,對我們後續學習響應式程式設計、rxjava、webflux等都是有好處的。非同步程式設計可以幫我們高效的利用系統資源。來源:https://www.tuicool.com/articles/r26jAfv