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 注解使用规则

  1. @Async 只能标注在Spring 管理的 Bean 方法上(如 @Service/@Component/@Controller 中的方法);
  2. 禁止在同一个类内部调用异步方法(Spring AOP 代理机制会失效,异步不生效);
  3. 异步方法参数传递与普通方法一致,支持基本类型、自定义对象、集合等;
  4. 建议显式指定线程池名称(@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 必须捕获的异常类型

  1. 运行时异常NullPointerExceptionIllegalArgumentExceptionArithmeticException 等;
  2. 检查型异常IOExceptionSQLException 等(需显式 catch 或 throws);
  3. 自定义业务异常:如 BizExceptionThirdPartyException 等。

4.2 异常处理必做操作

  1. 详细日志记录:必须包含「方法名、入参、异常类型、异常信息、堆栈」,便于问题排查;
  2. 业务补偿(可选):根据场景执行重试、数据回滚、告警通知(如钉钉/短信)、记录异常工单等;
  3. 避免吞异常:禁止仅 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 禁止操作

  1. 禁止在同一个类内部调用 @Async 方法(AOP 代理失效,异步不生效);
  2. 禁止使用 Spring 默认线程池(必须自定义有界队列线程池);
  3. 禁止异步方法抛出未捕获的异常(会导致异常丢失,仅打印默认日志);
  4. 禁止在异步方法中操作未加锁的共享变量(线程安全问题);
  5. 禁止传递易关闭的资源(如 InputStreamConnection),建议传递资源标识(如文件ID、数据库主键),异步方法内自行获取/关闭资源。

5.2 注意事项

  1. 参数线程安全:传递可变对象(如自定义 POJO)时,建议传递副本,避免主线程/异步线程同时修改导致数据错乱;
  2. 资源释放:异步方法内使用的资源(如文件流、数据库连接)必须在 try-finally 中关闭;
  3. 避免阻塞:Web 场景下,异步方法调用方(如 Controller)禁止调用 future.get() 阻塞,否则降低接口吞吐量;
  4. 线程池监控:生产环境建议监控线程池状态(核心线程数、活跃线程数、队列长度),避免线程池耗尽。

六、常见问题排查

6.1 异步方法不生效

  • 原因1:未加 @EnableAsync → 补充注解;
  • 原因2:内部调用异步方法 → 重构代码,通过其他 Bean 调用;
  • 原因3:方法非 Spring Bean → 确保方法所在类加 @Service/@Component
  • 原因4:方法为 private/static → 改为 public 非静态方法。

6.2 异步方法异常丢失

  • 原因:未在方法内 try-catch → 补充异常捕获逻辑;
  • 验证:查看日志是否有异常堆栈,无则说明异常未捕获。

6.3 线程池 OOM

  • 原因:使用无界队列/默认线程池 → 改为自定义有界队列线程池;
  • 优化:调整核心线程数、最大线程数、队列容量,适配业务并发量。

七、最佳实践总结

  1. 所有 @Async 方法必须指定自定义有界队列线程池;
  2. 异步方法异常必须在方法内 try-catch,记录详细日志 + 必要的业务补偿;
  3. 无返回值异步方法优先使用 void + 方法内 try-catch;
  4. 有返回值异步方法优先使用 CompletableFuture + 非阻塞异常处理;
  5. 避免异步方法阻塞主线程,尤其是 Web 场景;
  6. 异步方法参数传递优先使用不可变对象/副本,保证线程安全。

同一个类内部调用@Async方法失效原因

在Spring中,同一个类内部调用@Async标注的方法时,异步会失效(同步执行),核心原因是@Async基于Spring AOP代理实现:内部调用不会经过代理类,直接调用原对象方法,导致异步注解失效。

核心原理回顾

Spring AOP通过动态代理实现(JDK动态代理/CGLIB):

  • 外部调用@Async方法时,实际调用的是代理类的方法,代理类会将任务提交到线程池;
  • 内部调用时,this.xxx()直接调用原对象方法,跳过代理类,异步逻辑不执行。
posted @ 2025-12-12 19:18  露娜妹  阅读(3)  评论(0)    收藏  举报