@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 方法只能返回 void 或 Future(CompletableFuture、ListenableFuture)。返回普通对象会被忽略,甚至报错。

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. 异常要自己管:异步方法里一定要记录异常
相关推荐
AI你一生一世2 小时前
不止于时序存储:DolphinDB 如何成为 AI 时代的工业智能底座
java·人工智能·struts·时序数据库·工业智能·dolphindb·ai基础设施
云-缩影2 小时前
拆解 AI 图像生成应用的后端架构:ECS、OSS 和大模型 API 怎么配合
java·spring boot·后端
瑞克-桑切斯2 小时前
7、MySQL 慢查询排查与优化实战指南
java·服务器·数据库
AI开源软件4 小时前
SpringBoot3+Vue3 财务管理系统如何设计:从业务单据到总账的模块划分
spring boot·vue3·领域建模·会计凭证·ruoyi office·业财协同·财务系统设计
海的透彻6 小时前
dockerfile如何快速构建容器(java服务)
java·开发语言
code2cat7 小时前
Java工具篇之Gradle:用任务图组织构建与交付
java·开发语言·单元测试·gradle
后台模板学习7 小时前
大模型 Prompt 工程在电商订单系统中的踩坑记录
java·python·prompt
w_zero_one7 小时前
算法--构造
java·数据结构·算法
SL_staff8 小时前
制造业订单交付崩盘?用四层协同框架重建交付可信度(技术实践)
java·产品·全栈
SL_staff9 小时前
打破风控迭代死循环:用决策编排重构业务与技术协作链路
java·产品·全栈