Spring Boot异步编程疑问:未用@Async为何仍异步?@Async的作用是什么
首先澄清第一个误区:Spring Boot的REST接口默认根本不是异步执行
Spring Boot内嵌的默认Servlet容器(Tomcat)走的是经典同步阻塞模型:
- 每收到一个HTTP请求,就从容器自带的工作线程池里分配一个线程
- 这个线程会完整走完整个调用链路:解析参数→执行Controller逻辑→调用Service执行业务(包括数据库查询、你写的
Thread.sleep(5000))→组装响应返回给客户端 - 业务逻辑没跑完之前,这个线程会被一直占用,不会去处理其他请求
Spring MVC确实支持异步接口能力(比如返回Callable、DeferredResult),但这不是默认开启的——只要你的Controller方法直接返回普通POJO、没有用到异步返回类型,全链路都是同步阻塞逻辑,和你开没开@Async没关系。
那你为啥会觉得去掉@Async之后效果没变化?
这个是你测试时的两个关键误区导致的:
误区1:你的代码结构根本没真正发挥@Async的异步价值
你写的Service方法返回Future<List<Tenant>>,如果Controller层是常规写法,同步等待结果:
@GetMapping("/tenants") public List<Tenant> getTenants() throws InterruptedException, ExecutionException { // 同步等待异步方法执行完成才返回 return tenantService.getAllTenants().get(); }
这种场景下哪怕加了@Async:
- Tomcat的请求线程把方法调用提交到自定义的2线程池之后,不会直接释放,反而会卡在
future.get()那里一直等任务跑完 - 本质上Tomcat的线程还是被占用了,并没有实现“把业务执行和请求线程解耦”的异步效果
- 你观察到的“前2个请求执行、第3个排队”,仅仅是因为自定义线程池的最大线程数只有2,3个任务提交到池里自然有1个要排队——这个排队是业务任务层面的,不是异步非阻塞带来的效果
当你去掉@Async注解后,方法直接在Tomcat线程里同步执行5s的sleep,只要你测试时的并发量不大(3个请求),根本感知不到和之前的区别——因为两种写法下,Tomcat线程都会被5s的sleep占满,只是一个是在自定义池里跑任务等结果,一个是直接在Tomcat线程里跑任务。
误区2:对容器默认线程数的认知偏差
Spring Boot内嵌Tomcat的默认工作线程数是200,也就是说正常情况下,你连续发3个请求,会有3个不同的Tomcat工作线程同时处理这3个请求,根本不会出现“第3个排队等”的情况。如果你去掉@Async之后真的观察到了“2个执行、1个排队”,大概率是两种情况:
- 你之前为了测试自定义线程池,手动把Tomcat的工作线程数也改成了2
- 你用了同一个HTTP客户端(比如单标签浏览器、Postman单连接)发请求,受客户端本身的并发连接限制,3个请求根本没有同时打到服务端,造成了“排队”的假象
@Async注解的实际作用到底是什么
@Async的核心价值,根本不是“让接口变快”或者“让接口支持高并发排队”,它的本质是把方法的执行逻辑,从当前调用它的线程,剥离到独立配置的业务线程池里执行,实现线程资源的隔离:
- 真正发挥异步价值的场景,一定是调用方不需要等待方法执行结果的:比如用户注册接口返回成功之后,异步发欢迎邮件、写操作日志、同步用户数据到第三方系统——这时候调用方(Tomcat线程)把这些任务提交到@Async的线程池之后,就可以立刻返回响应,不会被慢任务占着
- 它可以帮你隔离不同类型业务的线程资源:比如把报表生成、批量数据处理这类CPU/IO密集的慢任务,放到独立配置的业务线程池里,就算这些任务把自己的池占满了,也不会抢Tomcat的请求处理线程,保证核心的接口响应能力不受影响
- 你现在配置的2线程自定义池,就算发挥了@Async的效果,也只会让这2个工作线程在后台跑慢任务,Tomcat线程根本不需要等这些任务跑完就可以返回——比如Controller层改成立刻返回“任务已提交”,等任务跑完再通过回调通知客户端,这时候才是真正的异步效果。
不同写法的实际测试差异
为了帮你彻底理清认知,你可以自己做对比测试,不同场景下的表现是完全不一样的:
- 无@Async、同步执行逻辑:3个请求过来,直接占3个Tomcat线程,每个线程sleep5s再返回,过程中如果再发197个请求,照样能正常拿到线程处理,不会排队(因为Tomcat默认200个工作线程)
- 有@Async、Controller层同步调用
future.get()(你现在的代码写法):3个请求过来,提交3个任务到只有2线程的自定义池,3个Tomcat线程卡在future.get()等结果,这时候所有调用这个接口的请求,都会因为自定义池容量不够排队——这也是你感觉和之前效果差不多的核心原因 - 有@Async、Controller层不等待方法执行结果(真正的异步):3个请求过来,Tomcat线程把任务提交到自定义池之后,立刻返回“任务已接收”,3个Tomcat线程马上就被释放,可以处理新的请求,后台只有2个自定义线程在慢慢跑5s的慢任务,根本不会影响接口的响应速度。
内容的提问来源于stack exchange,提问作者yakup geyik

