Spring异步机制原理、性能异常分析及配置与示例咨询
Spring异步机制原理与性能问题排查
一、核心问题分析
你用100个虚拟线程测试带@Async和CompletableFuture的/async接口时,QPS仅3.5;移除异步代码后同步版本QPS达27,远高于你计算的理论上限6次/秒。问题根源出在异步实现的错误用法上。
二、Spring异步机制核心原理
Spring的@Async基于AOP代理实现:
- 标注
@Async的方法会被异步代理拦截,方法逻辑提交到指定的Executor线程池执行 - 调用方无需等待方法执行完成,可立即返回继续处理其他请求
关键注意点:如果在@Async方法内部直接调用CompletableFuture.supplyAsync(),默认会使用JDK的ForkJoinPool.commonPool(),而非你配置的自定义线程池,这是导致性能瓶颈的核心原因。
三、现有代码的问题
- 线程池冲突:你配置了
newCachedThreadPool作为Spring异步线程池,但CompletableFuture.supplyAsync()默认使用公共ForkJoin池,其默认线程数为CPU核心数-1(即11个)。你的业务逻辑需sleep2秒,11个线程最多同时处理11个请求,100个请求需排队,再加上线程创建、上下文切换开销,最终QPS远低于预期。 - 双重异步冗余:
@Async已将整个方法提交到异步线程池,内部嵌套CompletableFuture.supplyAsync()属于重复异步操作,无端增加线程调度开销。
四、修复后的异步代码示例
1. 正确的异步接口实现
移除内部的CompletableFuture.supplyAsync(),直接在@Async方法中编写业务逻辑:
@Async @GetMapping("/async") public CompletableFuture<String> testAsync() { try { Thread.sleep(2000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return CompletableFuture.completedFuture("Result of long operation"); }
或更简洁的写法(Spring会自动将返回值包装为异步结果):
@Async @GetMapping("/async") public String testAsync() { try { Thread.sleep(2000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return "Result of long operation"; }
2. 优化线程池配置
newCachedThreadPool会无限制创建线程,高并发下易引发OOM,建议使用有界线程池:
@SpringBootApplication @EnableAsync public class TestBootApplication implements AsyncConfigurer { public static void main(String[] args) { SpringApplication.run(TestBootApplication.class, args); } @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(12); // 核心线程数与CPU核心数一致 executor.setMaxPoolSize(24); // 最大线程数根据业务调整 executor.setQueueCapacity(100); // 任务队列容量 executor.setThreadNamePrefix("Async-"); // 线程名前缀,便于排查问题 executor.initialize(); return executor; } }
五、Spring异步适用场景
- IO密集型任务:调用第三方API、数据库查询、文件读写等,此类任务大部分时间在等待IO,异步可释放Tomcat线程处理更多请求
- 后台耗时任务:发送邮件、生成报表、数据同步等无需用户等待结果的操作
- 并行处理独立任务:同时调用多个独立接口后合并结果,可通过
CompletableFuture.allOf()实现并行
并行任务示例
@Async public CompletableFuture<String> callServiceA() { // 模拟调用服务A的IO延迟 try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return CompletableFuture.completedFuture("Result from Service A"); } @Async public CompletableFuture<String> callServiceB() { // 模拟调用服务B的IO延迟 try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return CompletableFuture.completedFuture("Result from Service B"); } // 合并两个并行任务的结果 public String getCombinedResult() throws Exception { CompletableFuture<String> futureA = callServiceA(); CompletableFuture<String> futureB = callServiceB(); // 等待两个任务全部完成 CompletableFuture.allOf(futureA, futureB).get(); return futureA.get() + " | " + futureB.get(); }
六、同步QPS异常的说明
你计算的同步理论上限6次/秒(12线程/2秒),但实际测试达27次/秒,原因是Thread.sleep(2000)属于IO等待,Tomcat线程在sleep时会释放CPU,Tomcat默认线程池最大为200,因此可创建更多线程处理请求,实际QPS远高于CPU核心数计算的理论值。而你的异步实现因误用小线程池,反而限制了并发能力。
内容的提问来源于stack exchange,提问作者Steffano Aravico
相关产品推荐
相关产品推荐

