异步

场景

发送邮件、生成报表、调用第三方接口等操作耗时且调用方不关心结果,同步执行会拖慢主流程。@Async 让方法在独立线程执行,调用方立即返回。

开启与基本用法

在配置类上加 @EnableAsync,再给方法标 @Async

@Service
public class NotificationService {

    private static final Logger log = LoggerFactory.getLogger(NotificationService.class);

    @Async
    public void sendWelcomeMail(String to) {
        // 实际在异步线程执行,调用方不会阻塞在这里
        log.info("异步发送邮件给 {}", to);
    }
}

调用方照常调用即可,方法立即返回。需要返回结果时用 CompletableFuture

@Async
public CompletableFuture<String> buildReport() {
    String url = heavyWork();   // 耗时计算
    return CompletableFuture.completedFuture(url);
}

// 调用方:reportFuture.thenAccept(...) 或阻塞 get()

@Async 与 17 章事务、20 章定时任务共享同一个坑:同类内部自调用不走代理this.sendWelcomeMail(...) 会变成同步执行。异步逻辑要放到另一个受管 Bean 里,或注入自身代理。

线程池配置

@Async 有明确的执行器:Boot 会自动提供一个默认 ThreadPoolTaskExecutor(名为 applicationTaskExecutor),其核心/最大线程数、队列容量等参数由 spring.task.execution.* 配置项控制(属性与默认值的具体清单以官方文档为准)。更常见的是自定义并显式指定:

@Bean("mailExecutor")
public Executor mailExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(4);      // 常驻线程
    executor.setMaxPoolSize(8);       // 最大线程
    executor.setQueueCapacity(200);   // 等待队列容量
    executor.setThreadNamePrefix("mail-");
    executor.initialize();
    return executor;
}
@Async("mailExecutor")     // 指定使用上面的线程池
public void sendWelcomeMail(String to) { ... }

生产建议明确线程池参数并监控队列积压,避免任务堆积拖垮内存。

异常处理

  • 返回 CompletableFuture 的异步方法:异常封装在 Future 里,调用方通过 get/whenComplete 感知;
  • void 异步方法:异常默认只打到异步线程,调用方无从感知。应在方法内自行 try-catch 并记录日志,或实现 AsyncUncaughtExceptionHandler 统一处理(相关 API 的推荐写法以官方文档为准)。

线程上下文注意

跨线程时 ThreadLocal 不会自动传递:MDC 日志编号、Spring Security 用户上下文等需要显式传递(例如给线程池配 TaskDecorator 复制上下文),否则异步线程里取不到登录用户等信息。

小结

@EnableAsync + @Async 解决了「快速返回、后台执行」的需求。正确用法四要素:外部 Bean 调用(代理生效)、显式线程池、异常自处理、跨线程上下文按需传递。

笔记加载中…