问题
为什么不建议直接使用Spring的@Async?
答案
一、核心问题
直接使用 @Async 存在以下风险:
- 默认线程池配置不合理
- 异常处理困难
- 无法监控和管理
- 容易导致OOM(内存溢出)
- 事务失效问题
二、详细分析
问题1:默认线程池的隐患(最严重)
默认配置:
// Spring默认的SimpleAsyncTaskExecutor
@Async
public void asyncMethod() {
// 每次调用都创建新线程,不复用
}
源码分析:
// SimpleAsyncTaskExecutor.java
public void execute(Runnable task) {
// ❌ 每次都创建新线程
Thread thread = createThread(task);
thread.start();
}
问题:
- 不复用线程,每次调用都创建新线程
- 无界线程,可能创建大量线程导致OOM
- 无队列机制,无法控制并发数
- 无拒绝策略,无法保护系统
危险示例:
@Async
public void sendEmail(String email) {
// 发送邮件
}
// 调用1000次
for (int i = 0; i < 1000; i++) {
emailService.sendEmail("user" + i + "@example.com");
}
// ❌ 会创建1000个线程,系统崩溃
问题2:异常处理困难
异常被吞掉:
@Async
public void processData() {
// ❌ 异常无法被调用方捕获
throw new RuntimeException("处理失败");
}
// 调用方无法感知异常
try {
dataService.processData();
} catch (Exception e) {
// 永远不会执行
}
默认异常处理器:
// Spring默认只打印日志
public class SimpleAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
// ❌ 只打印日志,无其他处理
logger.error("Unexpected error occurred", ex);
}
}
问题3:无法监控和管理
@Async
public void importData(File file) {
// 无法知道有多少任务在执行
// 无法知道线程池状态
// 无法拒绝新任务
}
缺失的能力:
- 无法查看活跃线程数
- 无法查看队列大小
- 无法查看任务完成情况
- 无法手动停止或重启线程池
问题4:容易导致OOM
内存溢出场景:
@RestController
public class UploadController {
@Autowired
private FileService fileService;
@PostMapping("/upload")
public String upload(@RequestParam MultipartFile file) {
// 每次上传都异步处理
fileService.processFileAsync(file);
return "success";
}
}
@Service
public class FileService {
@Async
public void processFileAsync(MultipartFile file) {
// 大文件处理,占用大量内存
byte[] data = file.getBytes(); // 假设100MB
// ...处理逻辑
}
}
// ❌ 100个并发上传 = 10GB内存占用 = OOM
三、正确的使用方式
方案1:自定义线程池配置(推荐)
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数
executor.setCorePoolSize(10);
// 最大线程数
executor.setMaxPoolSize(50);
// 队列容量
executor.setQueueCapacity(200);
// 线程名前缀
executor.setThreadNamePrefix("async-");
// 拒绝策略:调用者运行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待任务完成后关闭
executor.setWaitForTasksToCompleteOnShutdown(true);
// 等待时间
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) -> {
log.error("异步任务执行失败,方法:{},参数:{}", method.getName(), params, ex);
// 可以发送告警、记录数据库等
};
}
}
使用:
@Async
public void asyncMethod() {
// 使用自定义的线程池
}
方案2:使用多个线程池
@Configuration
public class AsyncConfig {
// 邮件发送线程池
@Bean("emailExecutor")
public Executor emailExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("email-");
executor.initialize();
return executor;
}
// 文件处理线程池
@Bean("fileExecutor")
public Executor fileExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("file-");
executor.initialize();
return executor;
}
}
// 使用时指定线程池
@Service
public class EmailService {
@Async("emailExecutor")
public void sendEmail(String email) {
// 使用emailExecutor线程池
}
}
@Service
public class FileService {
@Async("fileExecutor")
public void processFile(File file) {
// 使用fileExecutor线程池
}
}
方案3:使用CompletableFuture(更灵活)
@Service
public class UserService {
@Autowired
@Qualifier("customExecutor")
private Executor executor;
public CompletableFuture<User> getUserAsync(Long id) {
return CompletableFuture.supplyAsync(() -> {
// 查询用户
return userMapper.selectById(id);
}, executor)
.exceptionally(ex -> {
// 异常处理
log.error("查询用户失败", ex);
return null;
});
}
}
// 调用方可以获取结果
CompletableFuture<User> future = userService.getUserAsync(1L);
User user = future.get(); // 阻塞获取结果
// 或者组合多个异步任务
CompletableFuture<User> userFuture = userService.getUserAsync(1L);
CompletableFuture<List<Order>> orderFuture = orderService.getOrdersAsync(1L);
CompletableFuture.allOf(userFuture, orderFuture)
.thenAccept(v -> {
User user = userFuture.join();
List<Order> orders = orderFuture.join();
// 处理结果
});
四、最佳实践清单
| 项目 | ❌ 不推荐 | ✅ 推荐 |
|---|---|---|
| 线程池 | 使用默认SimpleAsyncTaskExecutor | 自定义ThreadPoolTaskExecutor |
| 线程数 | 无限制 | 设置corePoolSize和maxPoolSize |
| 队列 | 无队列 | 设置queueCapacity |
| 拒绝策略 | 无拒绝策略 | 设置RejectedExecutionHandler |
| 异常处理 | 默认打印日志 | 自定义AsyncUncaughtExceptionHandler |
| 监控 | 无监控 | 暴露线程池指标到监控系统 |
| 关闭 | 立即关闭 | setWaitForTasksToCompleteOnShutdown(true) |
五、生产级配置示例
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Value("${async.executor.core-pool-size:10}")
private int corePoolSize;
@Value("${async.executor.max-pool-size:50}")
private int maxPoolSize;
@Value("${async.executor.queue-capacity:200}")
private int queueCapacity;
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setThreadNamePrefix("async-");
// 拒绝策略:调用者运行(降级方案)
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 优雅关闭
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
// 线程工厂(可设置优先级、是否守护线程等)
executor.setThreadFactory(new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "async-" + threadNumber.getAndIncrement());
t.setDaemon(false);
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
});
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) -> {
log.error("异步任务执行失败");
log.error("方法:{}", method.getName());
log.error("参数:{}", Arrays.toString(params));
log.error("异常:", ex);
// 发送告警
alertService.sendAlert("异步任务失败:" + method.getName());
};
}
}
六、答题总结
简洁回答:
不建议直接使用 @Async 的原因:
- 默认线程池不合理:使用
SimpleAsyncTaskExecutor,每次调用都创建新线程,不复用,容易OOM - 无法控制并发:无最大线程数限制,无队列,无拒绝策略
- 异常处理困难:异步方法的异常无法被调用方捕获,默认只打印日志
- 无法监控:无法查看线程池状态、任务执行情况
- 事务失效:与
@Transactional一起使用时事务不生效
正确做法:
- 自定义
ThreadPoolTaskExecutor,配置合理的线程数、队列、拒绝策略 - 实现
AsyncUncaughtExceptionHandler处理异常 - 使用多个线程池区分不同业务
- 或使用
CompletableFuture获得更灵活的控制
面试加分点: 能说出SimpleAsyncTaskExecutor的源码实现(每次创建新线程),以及生产环境如何配置线程池参数。