面试知识库
进阶

Spring-@Async原理#

一句话答案#

@Async 通过 AOP 代理将方法调用提交到线程池异步执行,底层由 AsyncAnnotationBeanPostProcessor 创建代理,默认使用 SimpleAsyncTaskExecutor(每次 new 线程,生产必须自定义线程池)。

核心要点

原理#

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

默认线程池的坑#

  • 默认用 SimpleAsyncTaskExecutor——每次调用 new 一个线程,不复用
  • 生产必须自定义线程池:

返回值处理#

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

异常处理#

  • void 方法的异常默认被吞掉(日志中不可见)
  • 需要实现 AsyncUncaughtExceptionHandler 捕获
  • 返回 Future 的方法异常在 future.get() 时抛出

失效场景#

与 @Transactional 类似:

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

@Async的底层原理和@Transactional一样,都是基于AOP代理实现的。当我们在配置类上加@EnableAsync后,Spring会注册AsyncAnnotationBeanPostProcessor,它在Bean初始化完成后检查是否有@Async注解的方法,有的话就创建代理对象。调用@Async方法时,代理拦截调用,将方法封装为Callable提交到线程池异步执行。这里有个很关键的坑:默认使用的SimpleAsyncTaskExecutor每次调用都new一个线程,不做线程复用,生产环境必须自定义ThreadPoolTaskExecutor并通过@Async指定线程池名称。失效场景和@Transactional几乎一致:同类内自调用不经过代理、非public方法、static或final方法都会失效。还有一个容易被忽视的问题是异常处理:void返回类型的异步方法异常默认被吞掉,日志中不可见,需要实现AsyncUncaughtExceptionHandler来捕获;返回CompletableFuture的方法异常则在get时抛出,推荐使用这种方式。

追问与易错

追问方向:

  • “默认线程池有什么问题?”→ SimpleAsyncTaskExecutor 每次 new 线程,不复用
  • “@Async 和 @Transactional 能一起用吗?”→ 能,但注意事务在新线程中是独立的

易错点:

  • ❌ “@Async 方法默认有线程池管理”——默认每次创建新线程
  • ❌ “void 方法异常会报错”——默认被吞掉