面试知识库
中 进阶

Spring-@Async原理#

一句话答案#

@Async 通过 AOP 代理将方法调用提交到线程池异步执行,底层由 AsyncAnnotationBeanPostProcessor 创建代理,纯 Spring 下找不到 TaskExecutor Bean 时回退到 SimpleAsyncTaskExecutor(每次 new 线程);Spring Boot 会自动配置 ThreadPoolTaskExecutor(applicationTaskExecutor),生产仍建议自定义线程池。

核心要点

原理#

1. @EnableAsync 导入 AsyncConfigurationSelector
2. 注册 AsyncAnnotationBeanPostProcessor
3. 该 BeanPostProcessor 在 Bean 初始化后检查是否有 @Async 方法
4. 有 → 创建代理对象(和 AOP 一样的代理机制)
5. 调用 @Async 方法时,代理拦截 → 封装为 Callable → 提交到线程池
plaintext

默认线程池的坑#

  • 纯 Spring:先找唯一的 TaskExecutor Bean 或名为 taskExecutor 的 Executor Bean,都没有才用 SimpleAsyncTaskExecutor——每次调用 new 一个线程,不复用
  • Spring Boot:没有自定义 Executor 时自动配置 ThreadPoolTaskExecutor(core 8,max 和队列容量默认都是 Integer.MAX_VALUE——队列无界所以 max 实际不生效,可通过 spring.task.execution.pool.* 改);开启虚拟线程(spring.threads.virtual.enabled=true,JDK 21+)时改用基于虚拟线程的 SimpleAsyncTaskExecutor
  • 默认池参数不一定贴合业务,且多个异步任务共用一个池会互相影响
  • 生产必须自定义线程池:

返回值处理#

返回类型行为
void异步执行,调用方无法获取结果
Future调用方可通过 future.get() 获取结果(会阻塞)
CompletableFuture推荐,支持链式编排

异常处理#

  • void 方法的异常调用方感知不到,默认只由 SimpleAsyncUncaughtExceptionHandler 打一条 ERROR 日志
  • 需要告警/补偿时实现 AsyncUncaughtExceptionHandler(通过 AsyncConfigurer 注册)
  • 返回 Future 的方法异常在 future.get() 时抛出

失效场景#

与 @Transactional 类似:

  1. 自调用(同类内调用不经过代理)
  2. 非 public 方法
  3. 没加 @EnableAsync
  4. 方法是 static 或 final

面试回答(2分钟版)

@Async的底层原理和@Transactional一样,都是基于AOP代理实现的。当我们在配置类上加@EnableAsync后,Spring会注册AsyncAnnotationBeanPostProcessor,它在Bean初始化完成后检查是否有@Async注解的方法,有的话就创建代理对象。调用@Async方法时,代理拦截调用,将方法封装为Callable提交到线程池异步执行。这里有个很关键的坑:纯Spring环境下找不到线程池Bean时会回退到SimpleAsyncTaskExecutor,每次调用都new一个线程,不做线程复用;Spring Boot虽然自动配置了ThreadPoolTaskExecutor(core 8、队列默认无界,任务多了只会排队不会扩线程),但生产环境仍建议按业务自定义ThreadPoolTaskExecutor并通过@Async指定线程池名称。失效场景和@Transactional几乎一致:同类内自调用不经过代理、非public方法、static或final方法都会失效。还有一个容易被忽视的问题是异常处理:void返回类型的异步方法异常调用方感知不到,默认只打一条ERROR日志,需要告警或补偿时实现AsyncUncaughtExceptionHandler;返回CompletableFuture的方法异常则在get时抛出,推荐使用这种方式。

追问与易错

追问方向:

  • “默认线程池有什么问题?”→ 纯 Spring 回退的 SimpleAsyncTaskExecutor 每次 new 线程,不复用;Spring Boot 自动配置的是 ThreadPoolTaskExecutor,但参数是通用默认值,且所有异步任务共用一个池
  • “@Async 和 @Transactional 能一起用吗?”→ 能,但注意事务在新线程中是独立的

易错点:

  • ❌ “@Async 方法默认一定有线程池管理”——纯 Spring 没配 Executor 时每次创建新线程(Spring Boot 才自动配线程池)
  • ❌ “void 方法异常会抛给调用方”——调用方拿不到,默认只打 ERROR 日志