@Async 什么时候会失效?
- 标签:Spring
🏛 业务架构师
@Async 失效场景我按根因分四组,每组都对应真实业务风险:
1. 代理问题(最高频):
- 同类内部调用:
this.sendNotify()不走代理,异步失效且无报错——静默变同步,大促时主流程被拖慢; - 方法非 public、或类没被容器管理(手动 new);
- final/static 方法:CGLIB 无法覆写。
2. 配置缺失:
- 忘加
@EnableAsync; - 返回值类型不匹配:返回
void或Future才有意义,声明其他类型拿不到真实结果。
3. 调用侧语义问题:
- 异常被吞:异步方法的异常不会传给调用方,无
AsyncUncaughtExceptionHandler时连日志都难找——发券失败无人知晓就是这类事故; - ThreadLocal 上下文丢失:用户身份、traceId、事务上下文在换线程后为空,异步日志断链、越权风险。
4. 线程池治理缺位:
- 用了默认的
SimpleAsyncTaskExecutor(不复用线程),高并发下线程数暴涨; - 队列无界堆积,任务延迟数小时——"异步了但更慢"。
治理总结:失效清单的每一项我都会转译成团队规范——异步方法独立成 Bean、必须自定义线程池 + 监控、上下文用 TaskDecorator 传递、不可丢的任务走 MQ 而不是 @Async。技术坑的尽头是工程纪律。
🏗 系统架构师
按根因分四类(自查清单):
- 代理未生效:没加
@EnableAsync(最常见);同类自调用绕过代理;方法非 public、final、static;类是 final(CGLIB 无法继承)。 - 线程池配置背锅(表现像失效):队列打满触发拒绝策略,DiscardPolicy 静默丢任务——我视静默丢弃为线上事故,必须换带日志与告警的拒绝策略,队列深度纳入监控。
- 语义误解:返回值应为
CompletableFuture,写 void 拿不到结果与异常;异步线程里的异常默认只走 AsyncUncaughtExceptionHandler,不配置等于吞掉。 - 组合语义错:
@Async加@Transactional同方法可以生效(两个切面叠加),但事务在异步线程内独立提交——外层回滚不影响它,这个语义常被误解成「失效」。
验证手段:压测后看线程名(task- 前缀)与埋点指标——「注解写了」和「真的异步了」要用数据确认,不能靠感觉。
💻 全栈开发专家
逐一列,都是踩出来的:
- 没加
@EnableAsync:注解静静躺着,全同步跑(最冤) - 同类自调用:
this.asyncMethod()不走代理(最高频) - 方法非 public 或被 static/final 修饰:代理拦不到或覆写不了
- 返回值不对:要拿结果必须
Future/CompletableFuture;void 方法的异常静默消失,要AsyncUncaughtExceptionHandler兜底 - 线程池配置问题:队列满 + AbortPolicy →
TaskRejectedException(表现为偶发"任务没执行");纯 Spring 环境默认SimpleAsyncTaskExecutor每次新建线程,高并发线程爆炸 - 上下文丢失:SecurityContext、事务、MDC traceId 不跟线程走,子线程里全是 null——
TaskDecorator显式传递 - 与事务叠加:
@Async方法里的@Transactional是新线程新连接的新事务,原事务上下文传不过去 - 手动 new 出来的对象:没被容器代理
排查套路:打线程名日志确认换线程 → 核对 @EnableAsync 和池名 → 看调用路径是否跨 Bean → 线程池指标看拒绝数。规范做法:异步方法独立 Bean + 命名线程池 + 统一异常处理器 + 指标监控。