Spring 异步方法实践
Spring @Async 注解使用规范文档
一、概述
@Async 是 Spring 提供的异步方法执行注解,可将方法提交到自定义线程池异步执行,避免主线程阻塞,提升系统吞吐量。本文档聚焦 @Async 核心使用规范,异常处理采用方法内 try-catch 方案(简单直接、适配绝大多数业务场景)。
二、核心前置条件
2.1 开启异步支持
在 Spring 启动类/配置类上添加 @EnableAsync 注解,开启异步功能:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync // 开启异步支持
public class AsyncApplication {
public static void main(String[] args) {
SpringApplication.run(AsyncApplication.class, args);
}
}
2.2 自定义线程池(必配,禁止使用默认线程池)
Spring 原生默认线程池(SimpleAsyncTaskExecutor)无复用、无界队列,易导致 OOM,必须自定义线程池:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class AsyncThreadPoolConfig {
/**
* 自定义异步线程池
* @return 线程池实例
*/
@Bean("asyncExecutor") // 线程池名称,@Async注解指定该名称使用此线程池
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数(默认活跃线程数)
executor.setCorePoolSize(5);
// 最大线程数(核心线程满+队列满后,扩容的最大线程数)
executor.setMaxPoolSize(10);
// 队列容量(有界队列,避免任务无限堆积导致OOM)
executor.setQueueCapacity(1000);
// 线程名称前缀(便于日志排查)
executor.setThreadNamePrefix("Async-");
// 拒绝策略(队列满+线程数达最大时,由提交任务的线程执行,避免任务丢失)
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 核心线程超时回收(默认核心线程不超时,可根据业务配置)
executor.setAllowCoreThreadTimeOut(true);
executor.setKeepAliveSeconds(60);
// 初始化线程池
executor.initialize();
return executor;
}
}
三、@Async 基础使用规范
3.1 注解使用规则
@Async只能标注在Spring 管理的 Bean 方法上(如@Service/@Component/@Controller中的方法);- 禁止在同一个类内部调用异步方法(Spring AOP 代理机制会失效,异步不生效);
- 异步方法参数传递与普通方法一致,支持基本类型、自定义对象、集合等;
- 建议显式指定线程池名称(
@Async("asyncExecutor")),避免混用默认线程池。
3.2 无返回值异步方法(最常用)
示例代码
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class AsyncBusinessService {
/**
* 无返回值异步方法(核心:方法内try-catch捕获所有异常)
* @param param1 业务参数1
* @param param2 业务参数2
*/
@Async("asyncExecutor") // 指定自定义线程池
public void asyncVoidMethod(String param1, Integer param2) {
// 核心:所有异常在方法内捕获,避免异常丢失
try {
// 1. 异步业务逻辑(如耗时IO、数据处理、第三方调用)
System.out.println("异步方法执行线程:" + Thread.currentThread().getName());
System.out.println("接收参数:param1=" + param1 + ", param2=" + param2);
// 模拟业务异常(如空指针、算术异常)
if (param1 == null || param1.isEmpty()) {
throw new IllegalArgumentException("param1不能为空");
}
int result = 100 / param2; // 模拟除数为0异常
// 2. 正常业务处理逻辑
System.out.println("异步方法执行完成,计算结果:" + result);
} catch (Exception e) {
// 异常处理核心逻辑:日志记录 + 业务补偿(可选)
System.err.println("【异步方法异常】method=asyncVoidMethod, param1=" + param1 + ", param2=" + param2);
System.err.println("异常类型:" + e.getClass().getSimpleName() + ", 异常信息:" + e.getMessage());
// 可选:业务补偿(如重试、告警、记录异常日志到数据库)
// compensationLogic(param1, param2, e);
// 可选:抛出自定义异常(若需上层感知,建议结合返回值场景)
}
}
}
调用示例(另一个Bean中调用)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AsyncController {
@Autowired
private AsyncBusinessService asyncBusinessService;
/**
* 调用异步方法(主线程不阻塞)
*/
@GetMapping("/async/void")
public String callAsyncVoidMethod() {
// 调用异步方法,主线程立即返回,不等待执行结果
asyncBusinessService.asyncVoidMethod("test", 0);
return "异步方法已提交执行";
}
}
3.3 有返回值异步方法
异步方法需返回结果时,使用 CompletableFuture(JDK8+ 推荐)或 Future,异常仍在方法内 try-catch 封装:
示例代码(返回 CompletableFuture)
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;
@Service
public class AsyncBusinessService {
/**
* 有返回值异步方法(异常仍在方法内捕获,封装到CompletableFuture)
* @param param 业务参数
* @return 异步结果
*/
@Async("asyncExecutor")
public CompletableFuture<String> asyncReturnMethod(String param) {
try {
System.out.println("有返回值异步方法执行线程:" + Thread.currentThread().getName());
// 模拟业务逻辑
if (param == null) {
throw new RuntimeException("参数不能为空");
}
String result = "异步处理结果:" + param;
// 返回成功结果
return CompletableFuture.completedFuture(result);
} catch (Exception e) {
// 异常处理:记录日志 + 封装异常到CompletableFuture
System.err.println("【异步方法异常】method=asyncReturnMethod, param=" + param);
System.err.println("异常信息:" + e.getMessage());
// 返回包含异常的CompletableFuture
return CompletableFuture.failedFuture(e);
}
}
}
调用示例(非阻塞处理结果/异常)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.CompletableFuture;
@RestController
public class AsyncController {
@Autowired
private AsyncBusinessService asyncBusinessService;
@GetMapping("/async/return")
public String callAsyncReturnMethod() {
// 调用有返回值异步方法
CompletableFuture<String> future = asyncBusinessService.asyncReturnMethod(null);
// 非阻塞处理结果/异常(推荐)
future.whenComplete((result, ex) -> {
if (ex != null) {
// 调用方感知异常(如记录日志、告警)
System.err.println("调用异步方法异常:" + ex.getMessage());
} else {
System.out.println("异步方法返回结果:" + result);
}
});
// 可选:阻塞获取结果(不推荐,会导致主线程阻塞)
// try {
// String result = future.get();
// } catch (Exception e) {
// System.err.println("获取异步结果异常:" + e.getMessage());
// }
return "有返回值异步方法已提交执行";
}
}
四、异常处理核心规范(方法内 try-catch)
4.1 必须捕获的异常类型
- 运行时异常:
NullPointerException、IllegalArgumentException、ArithmeticException等; - 检查型异常:
IOException、SQLException等(需显式 catch 或 throws); - 自定义业务异常:如
BizException、ThirdPartyException等。
4.2 异常处理必做操作
- 详细日志记录:必须包含「方法名、入参、异常类型、异常信息、堆栈」,便于问题排查;
- 业务补偿(可选):根据场景执行重试、数据回滚、告警通知(如钉钉/短信)、记录异常工单等;
- 避免吞异常:禁止仅 catch 异常但不做任何处理(至少打印日志)。
4.3 异常处理示例模板
@Async("asyncExecutor")
public void asyncMethod(String param) {
try {
// 业务逻辑
} catch (IllegalArgumentException e) {
// 参数异常:记录日志 + 提示参数错误
System.err.println("【参数异常】method=asyncMethod, param=" + param + ", msg=" + e.getMessage());
} catch (BizException e) {
// 业务异常:记录日志 + 业务补偿
System.err.println("【业务异常】method=asyncMethod, param=" + param + ", msg=" + e.getMessage());
compensationLogic(param); // 补偿逻辑
} catch (Exception e) {
// 通用异常:记录日志 + 告警
System.err.println("【通用异常】method=asyncMethod, param=" + param + ", msg=" + e.getMessage());
sendAlarm("异步方法执行失败:" + e.getMessage()); // 告警通知
e.printStackTrace(); // 打印堆栈,便于排查
}
}
五、禁止/注意事项
5.1 禁止操作
- 禁止在同一个类内部调用
@Async方法(AOP 代理失效,异步不生效); - 禁止使用 Spring 默认线程池(必须自定义有界队列线程池);
- 禁止异步方法抛出未捕获的异常(会导致异常丢失,仅打印默认日志);
- 禁止在异步方法中操作未加锁的共享变量(线程安全问题);
- 禁止传递易关闭的资源(如
InputStream、Connection),建议传递资源标识(如文件ID、数据库主键),异步方法内自行获取/关闭资源。
5.2 注意事项
- 参数线程安全:传递可变对象(如自定义 POJO)时,建议传递副本,避免主线程/异步线程同时修改导致数据错乱;
- 资源释放:异步方法内使用的资源(如文件流、数据库连接)必须在 try-finally 中关闭;
- 避免阻塞:Web 场景下,异步方法调用方(如 Controller)禁止调用
future.get()阻塞,否则降低接口吞吐量; - 线程池监控:生产环境建议监控线程池状态(核心线程数、活跃线程数、队列长度),避免线程池耗尽。
六、常见问题排查
6.1 异步方法不生效
- 原因1:未加
@EnableAsync→ 补充注解; - 原因2:内部调用异步方法 → 重构代码,通过其他 Bean 调用;
- 原因3:方法非 Spring Bean → 确保方法所在类加
@Service/@Component; - 原因4:方法为 private/static → 改为 public 非静态方法。
6.2 异步方法异常丢失
- 原因:未在方法内 try-catch → 补充异常捕获逻辑;
- 验证:查看日志是否有异常堆栈,无则说明异常未捕获。
6.3 线程池 OOM
- 原因:使用无界队列/默认线程池 → 改为自定义有界队列线程池;
- 优化:调整核心线程数、最大线程数、队列容量,适配业务并发量。
七、最佳实践总结
- 所有
@Async方法必须指定自定义有界队列线程池; - 异步方法异常必须在方法内 try-catch,记录详细日志 + 必要的业务补偿;
- 无返回值异步方法优先使用
void+ 方法内 try-catch; - 有返回值异步方法优先使用
CompletableFuture+ 非阻塞异常处理; - 避免异步方法阻塞主线程,尤其是 Web 场景;
- 异步方法参数传递优先使用不可变对象/副本,保证线程安全。
同一个类内部调用@Async方法失效原因
在Spring中,同一个类内部调用@Async标注的方法时,异步会失效(同步执行),核心原因是@Async基于Spring AOP代理实现:内部调用不会经过代理类,直接调用原对象方法,导致异步注解失效。
核心原理回顾
Spring AOP通过动态代理实现(JDK动态代理/CGLIB):
- 外部调用
@Async方法时,实际调用的是代理类的方法,代理类会将任务提交到线程池; - 内部调用时,
this.xxx()直接调用原对象方法,跳过代理类,异步逻辑不执行。
小LUA
面对敌人的严刑逼供,我一个字也没说,而是一五一十写了下来。

浙公网安备 33010602011771号