异步处理:Callable/DeferredResult/WebAsyncTask
同步接口里,Servlet 容器线程会一直占着等结果——如果业务要等第三方接口或跑批,线程就浪费在等待上。异步处理让容器线程先归还,等结果好了再由容器回调完成响应。
为什么需要异步
同步模型:1 个请求占用 1 个容器线程直到响应结束。慢任务多了线程池被占满,Tomcat 默认 200 线程很容易被打穿。异步模型:处理器启动一个「后台任务」后立刻返回,容器线程释放去接新请求;后台任务完成后容器再次派发(ASYNC dispatch)把结果写回。
Spring MVC 提供三种由浅入深的写法,前提是 Servlet 3.0+ 且容器/过滤器开启 async-supported。
Callable:最省事的异步
方法直接返回 Callable,框架用任务执行器去跑:
@RestController
public class AsyncApi {
@GetMapping("/api/slow")
public Callable<String> slow() {
return () -> {
Thread.sleep(3000); // 模拟慢业务
return "done after 3s";
};
}
}
// 请求进来立刻返回(容器线程释放),3 秒后容器再派发写出 "done after 3s"
默认执行器是 SimpleAsyncTaskExecutor(来一个任务开一个线程,生产不建议直接用),超时与执行器在 WebMvcConfigurer 中统一配置:
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
configurer.setDefaultTimeout(10_000); // 10 秒超时(毫秒)
configurer.setTaskExecutor(new ThreadPoolTaskExecutor()); // 换成有界线程池
}
DeferredResult:完全掌控完成时机
DeferredResult 更像「票据」:方法立刻返回一个空壳,业务代码在任意线程里调用 setResult 兑现它:
@RestController
public class TaskApi {
private final Map<String, DeferredResult<String>> pending = new ConcurrentHashMap<>();
@GetMapping("/api/task/start")
public DeferredResult<String> start(@RequestParam String taskId) {
DeferredResult<String> result = new DeferredResult<>(5000L); // 5 秒超时
result.onTimeout(() -> result.setErrorResult("timeout")); // 超时回调
pending.put(taskId, result);
return result; // 先返回,不阻塞容器线程
}
// 模拟其他线程(消息队列、调度任务)完成业务后兑现
public void finish(String taskId, String payload) {
DeferredResult<String> result = pending.remove(taskId);
if (result != null) {
result.setResult(payload); // 容器收到后派发写出响应
}
}
}
典型场景:长轮询、消息推送、任务状态查询——任务在别处跑,接口「挂」到结果上。
WebAsyncTask:Callable + 超时/回调管理
WebAsyncTask 给 Callable 加上了超时与完成回调:
@GetMapping("/api/web-async")
public WebAsyncTask<String> webAsync() {
Callable<String> callable = () -> {
Thread.sleep(2000);
return "ok";
};
WebAsyncTask<String> task = new WebAsyncTask<>(3000L, callable); // 3 秒超时
task.onTimeout(() -> "服务器繁忙,请稍后重试"); // 超时兜底文案
return task;
}
三种方式对比
| 方式 | 特点 | 适合 |
|---|---|---|
| Callable | 写法最接近同步,框架代跑 | 把同步慢逻辑异步化的快速改造 |
| DeferredResult | 结果由任意线程 setResult | 长轮询、跨系统回调、任务队列 |
| WebAsyncTask | Callable + 超时/回调 | 需要超时兜底的 Callable |
注意事项
- 异步只解决「等待时不占线程」,不加快业务本身;秒杀/下载等场景还要配合队列限流。
- 配了拦截器时,异步派发阶段 preHandle/postHandle 的调用次数与同步不同(存在并发开始回调),行为以官方文档为准。
- 过滤器若未开启 async-supported 会拦截不到二次派发或直接抛异常。
异步三板斧:容器线程「先还回去」、后台任务慢慢跑、结果就绪容器「再回来」写响应。简单慢逻辑用 Callable,需要外部线程兑现用 DeferredResult,要超时保护用 WebAsyncTask。