@Async 加了没生效?Spring Boot 异步编程全链路拆解:从线程池到自调用陷阱

@Async 加了没生效?Spring Boot 异步编程全链路拆解:从线程池到自调用陷阱

为什么你加了 @Async 方法还是同步执行?为什么异步方法一调用就报"找不到代理"?为什么线上偶发丢消息?这一篇从注解解析、代理生成、线程池选择到三大经典坑位,把 Spring Boot 异步全链路一次拆透。

一、@Async 到底干了什么?

先看最基础的用法:

java 复制代码
@Service
public class OrderService {

    @Async
    public void sendNotification(Long orderId) {
        // 模拟发短信、推送
        System.out.println(Thread.currentThread().getName()
            + " 发送通知: " + orderId);
    }
}

调用方:

java 复制代码
@RestController
public class OrderController {

    @Autowired
    private OrderService orderService;

    @PostMapping("/order")
    public String create(@RequestBody OrderDTO dto) {
        orderService.sendNotification(dto.getOrderId());  // 立即返回,不阻塞
        return "下单成功";
    }
}

如果你以为"加个注解就异步了",那说明你还没见过它翻车的样子。下面从原理拆起。

二、@Async 的执行原理:三步走

2.1 注解解析:AsyncAnnotationBeanPostProcessor

Spring 启动时,@EnableAsync 会注册一个 AsyncAnnotationBeanPostProcessor(Bean 后置处理器)。它在每个 Bean 实例化后检查:这个类上有没有 @Async 标注的方法?

swift 复制代码
Bean 实例化
    │
    ▼
AsyncAnnotationBeanPostProcessor.postProcessAfterInitialization
    │
    ├─ 扫描类中所有方法
    ├─ 找到带 @Async 的方法
    └─ 生成 CGLIB 代理,拦截该方法的调用

2.2 代理生成:CGLIB 动态代理

Spring 发现异步方法后,会为 Bean 生成一个 CGLIB 代理对象 。调用 orderService.sendNotification() 时,实际执行的是代理对象的拦截逻辑,而不是原始方法。

scss 复制代码
调用方 ──► orderService(代理对象)
              │
              ├─ 拦截器 AsyncExecutionInterceptor
              │     │
              │     ├─ 从线程池取线程(或创建)
              │     ├─ 提交任务到线程池
              │     └─ 立即返回(调用方不阻塞)
              │
              └─► 真实 TaskService.sendNotification()
                    在线程池中的某个线程上执行

关键点:代理对象是"包装"在 Bean 外面的。如果你拿到的是原始 Bean(未被代理),那 @Async 就是无效的。

2.3 线程池选择:三优先

AsyncExecutionInterceptor 选择线程池的顺序:

优先级 方式 说明
1 @Async("taskExecutor") 显式指定 方法上的 value 指定 Bean 名
2 自定义 TaskExecutor Bean 容器中唯一的一个
3 SimpleAsyncTaskExecutor 兜底:每个任务新建线程,无复用

很多人踩坑就在这:没有配置线程池,Spring 用 SimpleAsyncTaskExecutor 兜底,每次异步调用都 new 一个线程,高并发下直接打爆。

三、常见坑位:为什么 @Async 不生效?

坑位 1:自调用(this 调用)

同一个类内部调用异步方法,走的是 this.xxx()不会经过代理对象,@Async 直接失效。

java 复制代码
@Service
public class OrderService {

    // 坑!这个方法里调用自身的异步方法,不会生效
    public void handle(Order order) {
        sendNotification(order.getId());  // this.xxx() 非代理调用
    }

    @Async
    public void sendNotification(Long orderId) { ... }
}

解法 :把异步方法放到独立的 Service,从外部调用;或注入自身代理(@Lazy 注入 self)。

java 复制代码
@Service
public class OrderService {
    @Autowired
    @Lazy
    private OrderService self;   // 注入的是代理

    public void handle(Order order) {
        self.sendNotification(order.getId());  // 走代理,生效
    }
}

坑位 2:方法可见性

@Async 方法不能是 private,也不能是 final------CGLIB 代理无法覆盖它们。protected / public 都可以。

修饰符 是否生效 原因
public 代理可拦截
protected ✅(一般) 可拦截
private 代理无法重写
final 代理无法继承重写

坑位 3:返回值类型

@Async 方法只能返回 voidFutureCompletableFutureListenableFuture)。返回普通对象会被忽略,甚至报错。

java 复制代码
@Async
public CompletableFuture<String> queryInfo(Long id) {
    return CompletableFuture.completedFuture("结果");
}

坑位 4:异常吞掉

异步方法里的异常,不会抛给调用方。如果不在异步方法内 try-catch,异常静默丢失,排错困难。

java 复制代码
@Async
public void send(String msg) {
    try {
        // 业务
    } catch (Exception e) {
        log.error("异步任务失败", e);  // 必须自己记录
    }
}

四、正确姿势:自定义线程池 + 优雅配置

生产环境建议这样配:

java 复制代码
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Bean("bizExecutor")
    public ThreadPoolTaskExecutor bizExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(8);
        executor.setMaxPoolSize(16);
        executor.setQueueCapacity(200);
        executor.setThreadNamePrefix("biz-async-");
        // 关键:拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

CallerRunsPolicy 在队列满时让调用线程执行任务------牺牲一点性能,保证任务不丢失

五、异步 + 事务:一对冤家

@Async 和 @Transactional 叠加时要小心 :事务是绑定在线程上的(ThreadLocal)。@Async 把方法丢到另一个线程执行,事务上下文不传递,常见坑:异步方法里的事务不生效

java 复制代码
@Async
@Transactional
public void doAsyncWithTx() {
    // ⚠️ 事务和异步都在代理层,但异步线程上没有事务绑定
    // 更安全的做法:异步方法内部手动开启事务
}

解法:

  1. 异步方法里用 TransactionTemplate 手动控制;
  2. 或者先同步提交事务,再异步执行下游动作。

六、异步编排:CompletableFuture 的花式玩法

@Async 配合 CompletableFuture 可以玩出很多编排:

java 复制代码
@Async("bizExecutor")
public CompletableFuture<Integer> getA() { ... }
@Async("bizExecutor")
public CompletableFuture<Integer> getB() { ... }

// 并行执行,聚合结果
CompletableFuture.allOf(fa, fb).join();
int sum = fa.get() + fb.get();

七、@Async 全链路一张图

scss 复制代码
HTTP 请求线程 ──► OrderController.create()
                        │
                        ▼
               orderService (CGLIB 代理)
                        │
                        ├─ 同步方法 → 原线程执行
                        │
                        └─ 异步方法 → AsyncExecutionInterceptor
                                       │
                                       ▼
                               线程池 ThreadPoolTaskExecutor
                               ┌─────┬─────┬─────┬─────┐
                               │ t1  │ t2  │ t3  │ t4  │  ← 异步线程
                               └─────┴─────┴─────┴─────┘
                                       │
                                       ▼
                              TaskService.sendNotification()
                              异常 → 线程池拒绝策略 或 静默丢失

八、面试高频点总结

问题 答案
@Async 生效的前提 方法必须通过代理调用(外部调用)
线程池怎么选 @Async("beanName") > 唯一TaskExecutor > SimpleAsyncTaskExecutor
为什么自调用不生效 this 调用不经过代理
事务能跨线程吗 不能,ThreadLocal 绑定线程
异步异常怎么处理 方法内 try-catch,或 CompletableFuture.exceptionally

九、总结

@Async 是一个"看着简单、用着翻车"的注解。核心记住三点:

  1. 代理是关键:必须从外部走代理调用才生效
  2. 线程池必须显式配置:别用 SimpleAsyncTaskExecutor 兜底
  3. 异常要自己管:异步方法里一定要记录异常
相关推荐
zfoo-framework5 小时前
安装pi agent 支持duojie和deepseek
java·服务器·前端
君顾16 小时前
代驾跑腿业务系统实战开发指南
java·开发语言·代驾跑腿
NGINX开源社区6 小时前
NGINX Ingress Controller 5.5:安全性与性能提升,迁移更轻松
java·服务器·nginx
云烟成雨TD7 小时前
Micrometer 系列【25】Spring Boot Actuator | Spring MVC、Spring WebFlux 指标
java·spring boot·micrometer
liudashuang20177 小时前
Kafka Producer 隐藏深坑:Sender 线程自阻塞(自死锁)导致 BufferExhaustedException 完整复盘
java·分布式·kafka·linq
风筱7 小时前
Idea的CC GUI插件安装Claude Code SDK失败
java·ide·ai编程
m0_527034337 小时前
异步任务审核系统设计:消息队列、超时重试与失败补偿
java·大数据·开发语言
xbgRS7 小时前
springBoot项目配置加载优先级
java·spring boot
zzzll11117 小时前
Loop Engineering:循环工程的原理、实践与应用
java·数据库·python