傳統 Servlet 同步阻塞模型下,請求線程需等待業務邏輯(DB 查詢、外部 API 調用、耗時計算)完成才能響應,線程資源嚴重浪費。Servlet 3.0 引入異步機制,允許請求線程迅速釋放,業務邏輯轉至異步線程處理,完成後再寫回響應,大幅提升吞吐量。
@WebServlet(urlPatterns = "/async", asyncSupported = true)
public class AsyncServlet extends HttpServlet { ... }
<servlet>
<servlet-name>asyncServlet</servlet-name>
<servlet-class>com.example.AsyncServlet</servlet-class>
<async-supported>true</async-supported>
</servlet>
ServletRegistration.Dynamic dynamic = servletContext.addServlet("async", new AsyncServlet());
dynamic.setAsyncSupported(true);
dynamic.addMapping("/async");
調用 request.startAsync() 獲取 AsyncContext,它保存了原始 ServletRequest 和 ServletResponse 的引用,使異步線程能繼續讀取請求參數、寫入響應數據。
| 方法 | 說明 |
|---|---|
getRequest() | 獲取請求對象,異步環境下同步使用 |
getResponse() | 獲取響應對象 |
start(Runnable) | 提交異步任務到容器管理的線程池(推薦) |
complete() | 標記異步處理結束,提交響應 |
dispatch(path) | 異步轉發到其他 Servlet/JSP |
setTimeout(ms) | 設置異步處理超時時間,負數表示無超時 |
addListener(AsyncListener) | 註冊生命週期監聽器(onStartAsync, onComplete, onTimeout, onError) |
@WebServlet(urlPatterns = "/async", asyncSupported = true)
public class AsyncDemoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
final AsyncContext ac = req.startAsync();
ac.setTimeout(5000);
ac.start(() -> {
try {
TimeUnit.SECONDS.sleep(3); // 模擬耗時業務
PrintWriter out = ac.getResponse().getWriter();
out.write("異步處理完成");
out.flush();
ac.complete(); // 必須調用
} catch (Exception e) { e.printStackTrace(); }
});
// 主線程可立即返回或做其他事
resp.getWriter().write("請求已接收,異步處理中...");
resp.flushBuffer();
}
}
@WebServlet(urlPatterns = "/async-queue", asyncSupported = true)
public class AsyncQueueServlet extends HttpServlet {
private final BlockingQueue<AsyncContext> queue = new LinkedBlockingQueue<>(100);
private final ExecutorService workerPool = Executors.newFixedThreadPool(10);
@Override
public void init() {
for (int i = 0; i < 5; i++) workerPool.submit(this::processQueue);
}
private void processQueue() {
while (true) {
try {
AsyncContext ac = queue.take();
HttpServletRequest req = (HttpServletRequest) ac.getRequest();
HttpServletResponse resp = (HttpServletResponse) ac.getResponse();
// 業務邏輯
TimeUnit.SECONDS.sleep(2);
PrintWriter out = resp.getWriter();
out.write("隊列處理完成: " + req.getParameterMap());
out.flush();
ac.complete();
} catch (Exception e) { e.printStackTrace(); }
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
try {
queue.put(req.startAsync()); // 入隊,非阻塞
resp.getWriter().write("已加入異步隊列處理");
} catch (InterruptedException e) { e.printStackTrace(); }
}
}
首次請求保存 AsyncContext 到應用級 Map(key 為用戶 ID),後續 Ajax 請求從 Map 取出 AsyncContext 直接寫入響應,實現服務端主動推送。需注意併發安全與超時清理。
complete() 必須調用,否則連接懸掛導致泄漏;若異步轉發目標不支持異步,容器會自動調用。ServletRequest/ServletResponse 需確保線程安全,避免與主線程競爭。onTimeout 中應調用 ac.complete() 釋放資源。startAsync(),會拋出 IllegalStateException。