问题

为什么不建议直接使用Spring的@Async?

答案

一、核心问题

直接使用 @Async 存在以下风险:

  1. 默认线程池配置不合理
  2. 异常处理困难
  3. 无法监控和管理
  4. 容易导致OOM(内存溢出)
  5. 事务失效问题

二、详细分析

问题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 的原因:

  1. 默认线程池不合理:使用 SimpleAsyncTaskExecutor,每次调用都创建新线程,不复用,容易OOM
  2. 无法控制并发:无最大线程数限制,无队列,无拒绝策略
  3. 异常处理困难:异步方法的异常无法被调用方捕获,默认只打印日志
  4. 无法监控:无法查看线程池状态、任务执行情况
  5. 事务失效:与 @Transactional 一起使用时事务不生效

正确做法:

  • 自定义 ThreadPoolTaskExecutor,配置合理的线程数、队列、拒绝策略
  • 实现 AsyncUncaughtExceptionHandler 处理异常
  • 使用多个线程池区分不同业务
  • 或使用 CompletableFuture 获得更灵活的控制

面试加分点: 能说出SimpleAsyncTaskExecutor的源码实现(每次创建新线程),以及生产环境如何配置线程池参数。