瀏覽器/客戶端發起一個請求,Web服務器開啓一個線程來處理請求,當請求處理完成之後,Web服務器返回處理結果,這就是同步調用。html
本文及圖片參考(下同) - 木子旭:異步調用【WebAsyncTask】java
在普通的場景下,若是服務器負載不大,而且後端服務也給力,同步調用並無什麼問題。git
但在高併發場景下,請求服務端的線程總數是有限的,若是某個線程一直處於阻塞狀態,就會影響系統的吞吐量。github
所謂異步請求,就是在當前線程調用之後直接返回,繼續處理其餘任務,當前調用處理成功之後再經過一個回調線程來處理返回結果。web
舉個作家務的例子類比一下:以前你燒火作飯,就只能守在旁邊添柴加火,等飯作好了才能去擺放餐具;如今你買了一個電飯鍋,當你向電飯鍋添加了作飯必備的材料並下達了作飯的指令之後,電飯鍋就本身開始工做了,你就能夠抽身去擺放餐具了,等飯作好,電飯鍋會主動蜂鳴通知你來取飯,這樣作飯的效率就提升了不少,你的並行處理能力直線上升。spring
Spring MVC 3.2 之後的版本開始引入了基於 Servlet 3 的異步請求處理,能夠實現以下的異步調用。後端
下面經過一個簡單的同步和異步對比的例子,來快速演示WebAsyncTask的用法以及同步與異步調用的差別。瀏覽器
首先定義一個Controller,並添加了兩個方法來表明一次Web請求要進行的兩個任務,這兩個任務分別要執行3秒。springboot
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.WebAsyncTask;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
/** * WebAsyncTask * * @author ijiangtao * @create 2019-07-03 11:31 **/
@RestController
public class WebAsyncTaskController {
private Map<String, String> buildResult() {
System.out.println("building result");
Map<String, String> result = new HashMap<>();
try {
Thread.sleep(3 * 1000);
} catch (Exception e) {
e.printStackTrace();
}
for (int i = 0; i < 1 * 1000; i++) {
result.put(i + "-key", i + "value");
}
return result;
}
private void doTask() {
System.out.println("do some tasks");
try {
Thread.sleep(3 * 1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
複製代碼
@GetMapping("/r1")
public Map<String, String> r1() {
Instant now = Instant.now();
Map<String, String> result = buildResult();
doTask();
System.out.println("r1 time consumption: " + ChronoUnit.SECONDS.between(now, Instant.now()) + " seconds");
return result;
}
複製代碼
在同步執行請求的狀況下,Web請求處理中全部的任務都是並行的,最終的執行耗時是全部任務的總和。服務器
@GetMapping("/r2")
public WebAsyncTask<Map<String, String>> r2() {
Instant now = Instant.now();
Callable<Map<String, String>> callable = new Callable<Map<String, String>>() {
@Override
public Map<String, String> call() throws Exception {
return buildResult();
}
};
doTask();
WebAsyncTask<Map<String, String>> webAsyncTask = new WebAsyncTask<>(callable);
System.out.println("r2 time consumption: " + ChronoUnit.SECONDS.between(now, Instant.now()) + " seconds");
return webAsyncTask;
}
複製代碼
對於異步執行的Web請求,咱們經過實現Callable接口的call方法來定義Web請求返回結果的任務,並經過WebAsyncTask來執行任務,當調用任務之後當即返回,便可並行執行其餘任務,最終當WebAsyncTask執行完成之後,Web請求得到返回。
若是僅僅想實現異步和並行處理,使用JDK提供的Future機制也能夠實現,WebAsyncTask的魅力還在於提供了超時時間配置、異步任務執行器以及執行完成回調、執行異常和超時後的回調等。
package net.ijiangtao.tech.demo.webasynctask.controller;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.WebAsyncTask;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
/** * WebAsyncTask * * @author ijiangtao * @create 2019-07-03 11:31 **/
@RestController
public class WebAsyncTask2Controller {
@GetMapping("/r3")
public WebAsyncTask<Map<String, String>> r2() {
Instant now = Instant.now();
Callable<Map<String, String>> callable = new Callable<Map<String, String>>() {
@Override
public Map<String, String> call() throws Exception {
return buildResult();
}
};
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
executor.setThreadNamePrefix("WebAsyncTask2-");
WebAsyncTask<Map<String, String>> webAsyncTask = new WebAsyncTask<>(2 * 1000L, executor, callable);
webAsyncTask.onCompletion(new Runnable() {
@Override
public void run() {
System.out.println("Completion");
}
});
webAsyncTask.onError(new Callable<Map<String, String>>() {
@Override
public Map<String, String> call() throws Exception {
System.out.println("Error");
return new HashMap<>();
}
});
webAsyncTask.onTimeout(new Callable<Map<String, String>>() {
@Override
public Map<String, String> call() throws Exception {
System.out.println("Timeout");
Map<String, String> timeOutResutl = new HashMap<>();
timeOutResutl.put("timeout", "result");
return timeOutResutl;
}
});
doTask();
System.out.println("r2 time consumption: " + ChronoUnit.SECONDS.between(now, Instant.now()) + " seconds");
return webAsyncTask;
}
private Map<String, String> buildResult() {
System.out.println("building result");
Map<String, String> result = new HashMap<>();
try {
Thread.sleep(3 * 1000);
} catch (Exception e) {
e.printStackTrace();
}
for (int i = 0; i < 1 * 1000; i++) {
result.put(i + "-key", i + "value");
}
return result;
}
private void doTask() {
System.out.println("do some tasks");
try {
Thread.sleep(3 * 1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
複製代碼
代碼中的例子演示了超時onTimeout
之後的處理機制。
本文講解了WebAsyncTask
實現機制,WebAsyncTask
實現了Web請求的異步調用,主要目的是釋放容器線程,提升服務器的吞吐量,在大流量、高併發場景下 (如搶購等) 十分實用。