Skip to content

@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。技术坑的尽头是工程纪律。

🏗 系统架构师 ​

按根因分四类(自查清单):

  1. 代理未生效:没加 @EnableAsync(最常见);同类自调用绕过代理;方法非 public、final、static;类是 final(CGLIB 无法继承)。
  2. 线程池配置背锅(表现像失效):队列打满触发拒绝策略,DiscardPolicy 静默丢任务——我视静默丢弃为线上事故,必须换带日志与告警的拒绝策略,队列深度纳入监控。
  3. 语义误解:返回值应为 CompletableFuture,写 void 拿不到结果与异常;异步线程里的异常默认只走 AsyncUncaughtExceptionHandler,不配置等于吞掉。
  4. 组合语义错:@Async 加 @Transactional 同方法可以生效(两个切面叠加),但事务在异步线程内独立提交——外层回滚不影响它,这个语义常被误解成「失效」。

验证手段:压测后看线程名(task- 前缀)与埋点指标——「注解写了」和「真的异步了」要用数据确认,不能靠感觉。

💻 全栈开发专家 ​

逐一列,都是踩出来的:

  1. 没加 @EnableAsync:注解静静躺着,全同步跑(最冤)
  2. 同类自调用:this.asyncMethod() 不走代理(最高频)
  3. 方法非 public 或被 static/final 修饰:代理拦不到或覆写不了
  4. 返回值不对:要拿结果必须 Future/CompletableFuture;void 方法的异常静默消失,要 AsyncUncaughtExceptionHandler 兜底
  5. 线程池配置问题:队列满 + AbortPolicy → TaskRejectedException(表现为偶发"任务没执行");纯 Spring 环境默认 SimpleAsyncTaskExecutor 每次新建线程,高并发线程爆炸
  6. 上下文丢失:SecurityContext、事务、MDC traceId 不跟线程走,子线程里全是 null——TaskDecorator 显式传递
  7. 与事务叠加:@Async 方法里的 @Transactional 是新线程新连接的新事务,原事务上下文传不过去
  8. 手动 new 出来的对象:没被容器代理

排查套路:打线程名日志确认换线程 → 核对 @EnableAsync 和池名 → 看调用路径是否跨 Bean → 线程池指标看拒绝数。规范做法:异步方法独立 Bean + 命名线程池 + 统一异常处理器 + 指标监控。