中 进阶
Spring-@Async原理#
一句话答案#
@Async 通过 AOP 代理将方法调用提交到线程池异步执行,底层由 AsyncAnnotationBeanPostProcessor 创建代理,默认使用 SimpleAsyncTaskExecutor(每次 new 线程,生产必须自定义线程池)。
核心要点
原理#
1. @EnableAsync 导入 AsyncConfigurationSelector
2. 注册 AsyncAnnotationBeanPostProcessor
3. 该 BeanPostProcessor 在 Bean 初始化后检查是否有 @Async 方法
4. 有 → 创建代理对象(和 AOP 一样的代理机制)
5. 调用 @Async 方法时,代理拦截 → 封装为 Callable → 提交到线程池plaintext默认线程池的坑#
- 默认用
SimpleAsyncTaskExecutor——每次调用 new 一个线程,不复用 - 生产必须自定义线程池:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(200);
executor.setThreadNamePrefix("async-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
@Async("taskExecutor") // 指定线程池
public void sendEmail() { ... }java返回值处理#
| 返回类型 | 行为 |
|---|---|
| void | 异步执行,调用方无法获取结果 |
| Future | 调用方可通过 future.get() 获取结果(会阻塞) |
| CompletableFuture | 推荐,支持链式编排 |
异常处理#
- void 方法的异常默认被吞掉(日志中不可见)
- 需要实现
AsyncUncaughtExceptionHandler捕获 - 返回 Future 的方法异常在 future.get() 时抛出
失效场景#
与 @Transactional 类似:
- 自调用(同类内调用不经过代理)
- 非 public 方法
- 没加 @EnableAsync
- 方法是 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 方法异常会报错”——默认被吞掉