0%

异步处理

Servlet 3.0 异步处理:提升 Web 应用并发能力的关键机制

在传统的 Servlet 处理模型中,一个请求会占用容器线程直到响应完成,若遇到耗时操作(如数据库查询、远程接口调用),线程会被长期阻塞,导致线程资源耗尽,并发能力下降。Servlet 3.0 引入异步处理机制,允许线程在启动耗时操作后立即返回容器,待操作完成后再通过异步上下文处理响应,显著提升了资源利用率和并发吞吐量。本文将详细解析异步处理的原理、核心 API 及实践方式。

异步处理的核心原理

传统同步处理的问题

在同步处理中,容器为每个请求分配一个线程,线程会全程参与请求处理(接收请求→业务处理→生成响应)。若业务处理包含耗时操作(如等待数据库返回),线程会处于阻塞状态,无法处理其他请求,导致:

  • 线程资源浪费(阻塞时不做有效工作);
  • 高并发下线程池耗尽,新请求被拒绝。

异步处理的改进

异步处理允许线程在启动耗时操作后立即释放,返回容器处理其他请求;待耗时操作完成后,再通过新的线程(或回调)处理响应。流程如下:

  1. 容器线程接收请求,启动异步处理,获取 AsyncContext 对象;
  2. 容器线程将耗时操作(如数据库查询)委托给后台线程,自身立即返回容器;
  3. 后台线程完成耗时操作后,通过 AsyncContext 生成响应,结束异步处理。

核心价值:减少线程阻塞时间,提高线程利用率,支持更高的并发请求。

异步处理的核心 API

Servlet 3.0 通过 ServletRequestAsyncContext 接口提供异步处理能力,核心类和方法如下:

ServletRequest 中的异步方法

方法 作用描述
startAsync() 启动异步处理,返回 AsyncContext 对象(使用原始请求和响应)。
startAsync(ServletRequest, ServletResponse) 启动异步处理,使用包装后的请求 / 响应对象(如 ServletRequestWrapper)。
isAsyncStarted() 判断当前请求是否处于异步模式。
isAsyncSupported() 判断当前 Servlet 是否支持异步处理(需配置 asyncSupported=true)。
getAsyncContext() 获取当前请求的 AsyncContext 对象(仅在异步模式下有效)。

AsyncContext 接口(异步上下文)

AsyncContext 是异步处理的核心对象,封装了请求 / 响应的上下文,提供控制异步流程的方法:

方法 作用描述
dispatch() 将请求分派到原始 Servlet 或指定资源(如 JSP),用于异步完成后继续处理。
dispatch(String path) 分派到指定路径的资源(如 /async/result.jsp)。
complete() 结束异步处理,通知容器响应已完成(必须调用,否则响应会一直挂起)。
start(Runnable run) 将任务提交到容器的线程池执行(通常用于处理耗时操作,避免阻塞)。
addListener(AsyncListener listener) 注册异步监听器,监听异步处理的生命周期事件(如超时、完成、错误)。
setTimeout(long timeout) 设置异步处理超时时间(毫秒),超时后触发 AsyncListener.onTimeout()
getRequest() / getResponse() 获取异步上下文关联的请求 / 响应对象。

启用异步处理的配置

在 Servlet 中启用异步

需通过注解或 web.xml 配置 asyncSupported=true,声明 Servlet 支持异步处理:

(1)注解配置(Servlet 3.0+)
1
2
3
4
@WebServlet(urlPatterns = "/async", asyncSupported = true) // 关键配置
public class AsyncServlet extends HttpServlet {
// 实现异步处理逻辑
}
(2)web.xml 配置
1
2
3
4
5
6
7
8
9
<servlet>
<servlet-name>AsyncServlet</servlet-name>
<servlet-class>com.example.AsyncServlet</servlet-class>
<async-supported>true</async-supported> <!-- 启用异步 -->
</servlet>
<servlet-mapping>
<servlet-name>AsyncServlet</servlet-name>
<url-pattern>/async</url-pattern>
</servlet-mapping>

注意:若 Servlet 调用了过滤器(Filter),过滤器也需配置 asyncSupported=true,否则异步处理会失败。

异步处理的实现示例

基础示例:处理耗时操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
@WebServlet(urlPatterns = "/async", asyncSupported = true)
public class AsyncServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");

// 1. 启动异步处理,获取 AsyncContext
AsyncContext asyncContext = request.startAsync();
// 设置超时时间(30秒)
asyncContext.setTimeout(30000);

// 2. 注册异步监听器(可选,处理超时、完成等事件)
asyncContext.addListener(new AsyncListener() {
@Override
public void onComplete(AsyncEvent event) throws IOException {
System.out.println("异步处理完成");
}

@Override
public void onTimeout(AsyncEvent event) throws IOException {
event.getAsyncContext().getResponse().getWriter().write("处理超时");
event.getAsyncContext().complete(); // 结束异步
}

@Override
public void onError(AsyncEvent event) throws IOException {
System.out.println("异步处理错误:" + event.getThrowable());
}

@Override
public void onStartAsync(AsyncEvent event) throws IOException {
// 新的异步周期启动时触发(较少使用)
}
});

// 3. 提交耗时操作到容器线程池(避免阻塞当前线程)
asyncContext.start(() -> {
try {
// 模拟耗时操作(如数据库查询、远程调用)
Thread.sleep(5000); // 5秒耗时

// 4. 处理完成,生成响应
PrintWriter out = asyncContext.getResponse().getWriter();
out.write("异步处理完成!耗时:5秒");

// 5. 结束异步处理(必须调用,否则响应不会返回)
asyncContext.complete();
} catch (Exception e) {
e.printStackTrace();
}
});

// 4. 容器线程在此处立即返回,无需等待耗时操作完成
System.out.println("容器线程已释放,可处理其他请求");
}
}

关键流程解析

  • 启动异步request.startAsync() 标记请求进入异步模式,AsyncContext 保存请求 / 响应引用。
  • 耗时操作处理:通过 asyncContext.start(Runnable) 将耗时操作提交到容器线程池,当前 Servlet 线程立即返回。
  • 响应生成:耗时操作完成后,在 Runnable 中通过 asyncContext.getResponse() 获取响应对象,写入结果。
  • 结束异步:调用 asyncContext.complete() 通知容器响应已完成,释放资源。

基于 dispatch 的异步转发

若需将异步处理结果转发到 JSP 渲染,可使用 dispatch() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
asyncContext.start(() -> {
try {
// 耗时操作
Thread.sleep(3000);
// 设置属性,供 JSP 使用
asyncContext.getRequest().setAttribute("result", "处理完成");
// 转发到 result.jsp
asyncContext.dispatch("/result.jsp");
// 无需调用 complete(),dispatch 会自动触发
} catch (Exception e) {
e.printStackTrace();
}
});

result.jsp 中可直接获取属性:

1
2
<%@ page contentType="text/html;charset=UTF-8" %>
<h1><%= request.getAttribute("result") %></h1>

异步处理的适用场景与注意事项

1. 适用场景

  • 耗时操作:如数据库查询、第三方 API 调用、文件上传 / 下载等。
  • 高并发场景:需要最大化利用线程资源,支持更多并发请求(如秒杀系统、API 网关)。

2. 注意事项

  • 资源释放:必须调用 complete()dispatch() 结束异步处理,否则请求会一直挂起,导致连接泄漏。
  • 超时处理:通过 setTimeout() 设置合理超时时间,避免客户端长期等待;超时后需在监听器中手动结束异步。
  • 线程安全AsyncContextgetRequest()getResponse() 在多线程环境下需注意线程安全,避免并发修改。
  • 过滤器兼容:若请求经过过滤器,过滤器需配置 asyncSupported=true,否则会抛出 IllegalStateException
  • 响应提交:异步处理中,响应对象的状态由容器管理,避免手动调用 response.flushBuffer()response.close()

异步处理 vs 同步处理

特性 同步处理 异步处理
线程占用 全程占用线程(直到响应完成) 仅启动阶段占用线程,耗时操作期间释放
并发能力 低(线程易阻塞) 高(线程利用率高)
适用场景 短耗时操作(如简单计算) 长耗时操作(如远程调用、IO 操作)
复杂度 低(直接处理) 高(需管理异步生命周期)

欢迎关注我的其它发布渠道