Spring 异步方法实践

Spring @Async 注解使用规范文档

一、概述

@Async 是 Spring 提供的异步方法执行注解,可将方法提交到自定义线程池异步执行,避免主线程阻塞,提升系统吞吐量。本文档聚焦 @Async 核心使用规范,异常处理采用方法内 try-catch 方案(简单直接、适配绝大多数业务场景)。

二、核心前置条件

2.1 开启异步支持

在 Spring 启动类/配置类上添加 @EnableAsync 注解,开启异步功能:

java 复制代码
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,必须自定义线程池:

java 复制代码
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 无返回值异步方法(最常用)

示例代码
java 复制代码
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中调用)
java 复制代码
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)
java 复制代码
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);
        }
    }
}
调用示例(非阻塞处理结果/异常)
java 复制代码
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 异常处理示例模板

java 复制代码
@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()直接调用原对象方法,跳过代理类,异步逻辑不执行。
相关推荐
源代码•宸25 分钟前
GoLang八股(Go语言基础)
开发语言·后端·golang·map·defer·recover·panic
czlczl2002092525 分钟前
OAuth 2.0 解析:后端开发者视角的原理与流程讲解
java·spring boot·后端
颜淡慕潇33 分钟前
Spring Boot 3.3.x、3.4.x、3.5.x 深度对比与演进分析
java·后端·架构
布列瑟农的星空33 分钟前
WebAssembly入门(一)——Emscripten
前端·后端
小突突突2 小时前
Spring框架中的单例bean是线程安全的吗?
java·后端·spring
iso少年2 小时前
Go 语言并发编程核心与用法
开发语言·后端·golang
掘金码甲哥2 小时前
云原生算力平台的架构解读
后端
码事漫谈2 小时前
智谱AI从清华实验室到“全球大模型第一股”的六年征程
后端
码事漫谈2 小时前
现代软件开发中常用架构的系统梳理与实践指南
后端
Mr.Entropy3 小时前
JdbcTemplate 性能好,但 Hibernate 生产力高。 如何选择?
java·后端·hibernate