@Async不生效主因是同一类内调用、非public方法、非Spring管理Bean、未配置线程池等;需确保外部调用、public方法、Spring容器托管、指定ThreadPoolTaskExecutor并验证线程名。

@Async 注解不生效的常见原因
直接在同一个类里调用 @Async 方法,几乎必然失效——Spring 的代理机制只对“外部 Bean 调用”起作用。这不是 bug,是 AOP 代理的天然限制。
- 方法必须是 public 的,private/protected 方法加了注解也无效
- 目标类必须由 Spring 容器管理(即不能 new 出来)
- 调用方和被调用方不能是同一个 Bean 实例(否则绕过代理)
- 如果用了 CGLIB 代理,需确保类不是 final 的,方法也不能是 final/static
典型错误现象:@Async 方法执行时主线程仍阻塞、日志没进异步线程、Thread.currentThread().getName() 还是 main 或 tomcat-xxx
自定义线程池为什么必须显式配置
Spring 默认用 SimpleAsyncTaskExecutor,它不复用线程,每次新建线程,高并发下容易 OOM。真要异步,必须配一个真正的线程池。
-
ThreadPoolTaskExecutor是最常用选择,支持 core/max pool size、队列容量、拒绝策略 - 别直接用
Executors.newFixedThreadPool()—— 它的无界队列 + 不可配置性,在 Spring 环境里属于反模式 - 线程名建议设前缀(如
setThreadNamePrefix("async-task-")),方便排查日志归属 - 拒绝策略推荐
CallerRunsPolicy,避免任务丢失,但要注意它会让调用线程自己执行,可能拖慢上游
示例配置片段:
@Bean("taskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(8);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-task-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
@Async 方法的返回值与异常处理陷阱
@Async 方法返回 void 或 Future<?>,但两者行为差异很大,且异常默认不抛给调用方。
- 返回
void:异常会被吞掉,仅记录在日志里(ERROR 级别),调用方完全感知不到失败 - 返回
Future<T>:必须手动调用future.get()才会触发异常传播,否则照样静默 - 若用
CompletableFuture,得配合handle()或exceptionally()显式处理,不能依赖 try-catch 包裹异步调用 - Spring 不会自动为
@Async方法织入事务,即使方法上有@Transactional,也大概率不在同一事务上下文
常见错误现象:异步方法里数据库更新失败,但主流程没报错、数据也没写进去、日志里只有一行 “java.lang.RuntimeException: xxx” 埋在角落
如何确认异步真正跑在指定线程池上
光看代码配了线程池不够,得验证实际执行线程是否来自你定义的 Bean。
- 在
@Async方法开头打日志:log.info("Running on thread: {}", Thread.currentThread().getName()); - 检查输出是否匹配你设置的
threadNamePrefix(比如async-task-1) - 别只测单次调用——多压几次,观察线程名是否轮转(如
async-task-1→async-task-2),确认复用逻辑生效 - 如果看到
ForkJoinPool.commonPool-worker-x或main,说明根本没走你的线程池,大概率是代理没生效或没指定 executor
指定线程池的方式有两种:全局默认(@EnableAsync(proxyTargetClass = true) 配 taskExecutor Bean),或方法级(@Async("taskExecutor"))。后者优先级更高,但别混用,容易漏掉配置。
线程池大小不是越大越好,尤其涉及 DB 或远程调用时,盲目扩大会加剧连接竞争和上下文切换开销。真实场景里,先从 2–4 核心线程起步,再按监控(如 active count、queue size、reject count)逐步调优。










