











Servlet 3.0(Java EE 6)开始引入了异步请求(Asynchronous Request Processing)机制,Tomcat 从 7.x 开始支持该特性。
在传统 Servlet 模型中,一个 HTTP 请求会一直占用一个 Tomcat 工作线程,直到业务处理完成并返回响应。
客户端
│
▼
Tomcat 工作线程
│
├──业务处理(可能几十秒)
│
└──返回响应
如果业务处理时间较长(例如调用远程接口、AI 大模型、消息等待等),Tomcat 工作线程会一直处于阻塞状态。
异步请求的核心思想是:
让处理请求的 Tomcat 工作线程尽快归还线程池,由其他线程继续完成业务,并返回响应。
因此,一个请求生命周期中可能会涉及多个线程:
对应流程如下:
客户端
│
▼
Tomcat线程(T1)
│
startAsync()
│
释放Tomcat线程
│
──────────────
业务线程(T2)
│
业务处理
│
dispatch()/complete()
│
▼
客户端
需要注意:
异步请求并不会让业务自动变快,它只是减少了 Tomcat 工作线程的占用时间,提高服务器的并发能力。
Tomcat 异步请求主要适用于耗时操作。
典型场景包括:
例如:
等待远程响应期间,没有必要一直占用 Tomcat 线程。
例如:
可以让业务线程完成处理,再恢复请求。
例如:
模型生成答案通常持续数秒甚至数分钟,非常适合异步请求。
如果结合:
还可以实现流式输出。
Server-Sent Events:
服务器持续向浏览器推送数据。
Spring MVC 中:
SseEmitter
底层就是基于 Servlet AsyncContext。
主要流程如下:
@WebServlet开启异步支持Servlet中调用startAsync()方法开启异步complete()方法结束异步过程开启异步:
@WebServlet(value = "/async", asyncSupported = true)
public class AsyncServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
// 开启异步, 这样容器就不会在servlet执行完后主动关闭响应流了
AsyncContext asyncContext = request.startAsync();
executor.execute(() -> {
response.setContentType("text/plain");
try {
// doSomething(耗时操作)
response.getWriter().write("Hello Async");
} catch (Exception e) {
try {
response.getWriter().write("failed");
} catch (IOException ex) {
throw new RuntimeException(ex);
}
} finally {
// 告知servlet容器可以关闭响应了
asyncContext.complete();
}
});
}
}
在这个例子中,由于servlet本身没有任何耗时动作,servlet执行完后,tomcat工作线程就会被释放掉,但是对应的响应流是没有被关闭的,只有调用asyncContext.complete()后,容器才会执行清理动作,关闭响应流。
当前servlet中的业务线程可以做一些预处理逻辑,然后再转发给另外一个servlet完成最终的响应,spring mvc中的异步底层都是这么玩的。进行转发后,就不用再调用asyncContext.complete()了,转发后的servlet执行完,容器会自动调用syncContext.complete()。
Async2Servlet.java
@Slf4j
@WebServlet(value = "/async2", asyncSupported = true)
public class Async2Servlet extends HttpServlet {
private final ExecutorService executor = Executors.newFixedThreadPool(10);
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
log.info("{}", request);
// 开启异步
AsyncContext asyncContext = request.startAsync();
executor.execute(() -> {
// doSomething(预处理逻辑)
// 通过保存预处理结果到request中
request.setAttribute("result", "success");
// 转发给/async3 servlet (不带参数则是转发给自己)
asyncContext.dispatch("/async3");
});
}
}
Async3Servlet.java
@Slf4j
@WebServlet(value = "/async3")
public class Async3Servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
// request为ApplicationHttpRequest实例
if (request instanceof HttpServletRequestWrapper) {
log.info("{}", ((HttpServletRequestWrapper)request).getRequest());
}
response.setContentType("text/plain");
// 获取预处理结果
String result = (String) request.getAttribute("result");
response.getWriter().write(result);
}
}
转发后的servlet接收到的request、response都被包了一层,分别为ApplicationHttpRequest、ApplicationHttpResponse,但是内部持有的request以及response和转发之前的是同一个对象,因此可以拿到之前的请求参数,同一个响应流。
Async3Servlet的响应结果就是请求Async2Servlet的结果。
Tomcat 异步请求支持超时控制,默认为30s,也可以单独设置,如下所示
AsyncContext async = request.startAsync();
async.setTimeout(30000);
超时后会触发:
async.addListener(new AsyncListener() {
@Override
public void onTimeout(AsyncEvent event) {
// 超时回调可以写响应,也可以进行转发,响应超时之后的结果
}
});
需要注意的是:
超时示例
@Slf4j
@WebServlet(value = "/async4", asyncSupported = true)
public class Async4Servlet extends HttpServlet {
private final ExecutorService executor = Executors.newFixedThreadPool(10);
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
// 开启异步
AsyncContext asyncContext = request.startAsync();
// 设置1秒超时
asyncContext.setTimeout(1000);
Future<?> future = executor.submit(() -> {
boolean isInterrupted = false;
try {
// 模拟超时操作
TimeUnit.SECONDS.sleep(3);
// 保存预处理结果到request中
request.setAttribute("result", "success");
} catch (InterruptedException e) {
isInterrupted = true;
}
/*
* 如果被中断了, 说明超时了, 就不要再调用转发了, 此时异步过程已经超时结束了,再调用会报错
* 超时回调中,会将future取消,进而中断线程
*/
if (!isInterrupted) {
asyncContext.dispatch("/async3");
}
});
asyncContext.addListener(new AsyncListener() {
@Override
public void onComplete(AsyncEvent event) {
log.info("======= onComplete=========");
}
@Override
public void onTimeout(AsyncEvent event) {
/*
* 这里回调如果不自己处理超时的响应, servlet容器会转发给统一的错误处理
* spring boot中为/error
*/
// 中断异步线程, 取消任务
log.info("======= onTimeout=========");
future.cancel(true);
request.setAttribute("result", "timeout");
asyncContext.dispatch("/async3");
}
@Override
public void onError(AsyncEvent event) {
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException {
}
});
}
}
Tomcat 异步请求的核心价值不是提高单个请求的执行速度,而是提高服务器整体吞吐量和线程利用率。
对于存在大量等待时间的业务(如 RPC、数据库、AI、大文件下载、SSE、长轮询等),异步请求能够显著减少 Tomcat 工作线程的占用,提高系统的并发处理能力。
在 Spring MVC 中,开发者通常无需直接操作 AsyncContext,而是使用 DeferredResult、Callable、ResponseBodyEmitter、SseEmitter 等高级抽象即可享受 Servlet 异步机制带来的优势。
理解其底层原理,有助于正确处理线程切换、超时控制、请求恢复以及流式响应等高级场景。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。