为何需要异步Servlet?基于Spring Boot嵌入式Tomcat8的MVC场景问询
Spring Boot + 嵌入式Tomcat 8 阻塞API的线程优化方案
作为一直在Spring Boot MVC项目里摸爬滚打的开发者,你提到的这个痛点我太熟悉了——Tomcat连接池里的线程被阻塞API死死占着,啥也干不了只能等,结果就是后续请求排队,系统吞吐量直接拉胯。不过好在Spring给我们提供了Callable和DeferredResult这两个神器,能完美解决这个问题,我给你详细唠唠:
核心逻辑:把阻塞任务从Tomcat线程转移出去
不管用哪种方式,核心思路都是让Tomcat的请求线程尽早释放回连接池,把耗时的阻塞操作交给我们自己管控的线程池去处理,这样Tomcat线程就能去处理更多请求,提升并发能力。
1. 使用Callable处理同步阻塞场景
如果你的业务是「先做少量同步操作,再调用阻塞API,最后返回结果」,Callable是最省心的选择。它会自动把阻塞逻辑提交到你配置的ExecutorService线程池里执行,Tomcat线程不用等阻塞完成就能先回去干活。
举个简单的代码示例:
@GetMapping("/async-callable") public Callable<String> handleAsyncRequest() { return () -> { // 这里替换成你的阻塞API调用,比如远程接口调用、慢SQL查询 Thread.sleep(5000); // 模拟5秒阻塞操作 return "Callable异步处理完成!"; }; }
- 如果你没有自定义
ExecutorService作为Bean,Spring会默认使用Tomcat的异步线程池,或者为每个请求创建新线程(这很危险,容易OOM),所以一定要自己配置线程池Bean,比如:
@Bean public ExecutorService customAsyncExecutor() { return new ThreadPoolExecutor( 4, // 核心线程数 8, // 最大线程数 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100), new ThreadFactoryBuilder().setNameFormat("async-pool-%d").build() ); }
2. 使用DeferredResult处理异步事件驱动场景
如果你的业务需要等待某个异步事件(比如消息队列的回调、第三方Webhook通知)才能返回结果,DeferredResult就更合适了。它允许你先把请求挂起,释放Tomcat线程,等异步结果就绪后再唤醒请求并返回响应。
代码示例:
@GetMapping("/async-deferred") public DeferredResult<String> handleDeferredRequest() { // 设置60秒超时,避免请求无限挂起 DeferredResult<String> deferredResult = new DeferredResult<>(60000L); // 用自定义线程池执行异步任务 customAsyncExecutor.execute(() -> { try { // 模拟等待异步事件(比如监听MQ消息) Thread.sleep(5000); // 结果就绪,返回响应 deferredResult.setResult("DeferredResult异步处理完成!"); } catch (InterruptedException e) { // 处理异常或超时 deferredResult.setErrorResult("处理超时或发生错误"); } }); return deferredResult; }
这里一定要记得设置超时时间,不然如果异步任务一直没结果,请求会一直占用资源。
关键注意点
- 自定义线程池时,要根据你的业务场景和服务器配置合理设置参数(核心线程数、最大线程数、队列大小),避免线程过多导致CPU上下文切换频繁,或者队列过大导致内存溢出。
- 不管用
Callable还是DeferredResult,都要做好异常处理和超时控制,避免出现请求挂死或者资源泄漏的情况。
内容的提问来源于stack exchange,提问作者Almas Abdrazak
相关产品推荐
相关产品推荐

