首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >深度解析@Async注解:从实战应用到底层原理,避坑指南全攻略

深度解析@Async注解:从实战应用到底层原理,避坑指南全攻略

作者头像
果酱带你啃java
发布2026-04-14 14:08:08
发布2026-04-14 14:08:08
400
举报

在Java开发领域,异步编程是提升系统吞吐量、优化用户体验的核心手段之一。而Spring框架提供的@Async注解,更是让开发者无需深入了解复杂的线程池原理,就能轻松实现异步调用。但实际开发中,很多同学在使用@Async时会遇到“异步不生效”“线程池耗尽”“事务失效”等问题。本文将从实战出发,结合底层源码,全面拆解@Async注解的使用方法、核心原理、避坑要点,搭配可直接运行的实例,让你真正吃透异步编程的精髓。

一、@Async注解核心认知:什么是异步调用?为什么需要它?
1.1 同步VS异步:本质区别

在讲解@Async之前,我们先明确同步调用与异步调用的核心差异:

  • 同步调用:方法A调用方法B后,必须等待方法B执行完毕并返回结果,A才能继续执行,整个过程是阻塞的。
  • 异步调用:方法A调用方法B后,无需等待B执行完毕,A可以直接继续执行后续逻辑;而B会在独立的线程中异步执行。
1.2 异步调用的适用场景

异步调用适合处理“耗时且非核心流程”的操作,典型场景包括:

  • 接口响应后的日志记录、数据统计(如用户登录后记录登录日志);
  • 邮件/短信发送(无需等待发送结果返回给前端);
  • 大文件导出、数据批量处理(避免阻塞主线程导致接口超时);
  • 第三方接口调用(如调用支付回调接口,无需同步等待结果)。
1.3 @Async的核心作用

Spring的@Async注解基于AOP实现,通过动态代理机制,将被注解的方法封装到独立的线程中执行,从而实现异步调用。其核心价值在于:

  • 简化异步编程:无需手动创建线程池、管理线程生命周期;
  • 解耦线程管理与业务逻辑:开发者只需关注业务实现,线程池配置统一管理;
  • 支持灵活配置:可自定义线程池参数、异常处理机制。
二、@Async基础使用:从环境搭建到第一个异步程序
2.1 环境依赖准备(Maven)

使用@Async需依赖Spring核心包,结合实战场景,我们搭建一个Spring Boot项目,核心依赖如下(所有版本采用最新稳定版):

代码语言:javascript
复制
<dependencies>
    <!-- Spring Boot核心依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>3.2.5</version>
    </dependency>
    <!-- Spring Boot测试依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <version>3.2.5</version>
        <scope>test</scope>
    </dependency>
    <!-- Lombok:简化日志、Getter/Setter等 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.30</version>
        <scope>provided</scope>
    </dependency>
    <!-- FastJSON2:JSON处理 -->
    <dependency>
        <groupId>com.alibaba.fastjson2</groupId>
        <artifactId>fastjson2</artifactId>
        <version>2.0.49</version>
    </dependency>
    <!-- Guava:集合工具类 -->
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>33.2.1-jre</version>
    </dependency>
    <!-- MyBatis-Plus:持久层框架 -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.5</version>
    </dependency>
    <!-- MySQL驱动 -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>8.3.0</version>
        <scope>runtime</scope>
    </dependency>
    <!-- Swagger3:接口文档 -->
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        <version>2.5.0</version>
    </dependency>
</dependencies>
2.2 启用@Async:@EnableAsync注解

要让Spring识别@Async注解,必须在配置类或启动类上添加@EnableAsync注解,该注解的作用是开启Spring的异步方法支持,底层会注册异步方法处理器。

代码语言:javascript
复制
package com.jam.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

/**
 * 应用启动类
 * 开启异步支持:@EnableAsync
 * @author ken
 */
@SpringBootApplication
@EnableAsync
public class AsyncDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(AsyncDemoApplication.class, args);
    }
}
2.3 第一个异步程序:基础使用示例
2.3.1 异步服务接口与实现

定义异步服务接口,在实现类的方法上添加@Async注解,标记该方法为异步方法。

代码语言:javascript
复制
package com.jam.demo.service;

/**
 * 异步服务接口
 * @author ken
 */
public interface AsyncService {
    /**
     * 基础异步方法:无返回值
     * @param taskName 任务名称
     */
    void basicAsyncTask(String taskName);

    /**
     * 异步方法:有返回值(返回Future)
     * @param taskName 任务名称
     * @param sleepTime 模拟耗时时间(毫秒)
     * @return 任务执行结果
     */
    Future<String> asyncTaskWithReturn(String taskName, long sleepTime);
}
代码语言:javascript
复制
package com.jam.demo.service.impl;

import com.jam.demo.service.AsyncService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

/**
 * 异步服务实现类
 * @author ken
 */
@Service
@Slf4j
publicclass AsyncServiceImpl implements AsyncService {

    /**
     * 基础异步方法:无返回值
     * @Async:标记该方法为异步方法,使用默认线程池
     * @param taskName 任务名称
     */
    @Override
    @Async
    public void basicAsyncTask(String taskName) {
        log.info("【异步任务】{} 开始执行,当前线程:{}", taskName, Thread.currentThread().getName());
        // 模拟耗时操作(如日志记录、邮件发送)
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            log.error("【异步任务】{} 执行异常", taskName, e);
            Thread.currentThread().interrupt();
        }
        log.info("【异步任务】{} 执行完毕", taskName);
    }

    /**
     * 异步方法:有返回值
     * 注意:有返回值的异步方法必须返回Future或其实现类(如FutureTask)
     * @param taskName 任务名称
     * @param sleepTime 模拟耗时时间(毫秒)
     * @return 任务执行结果
     */
    @Override
    @Async
    public Future<String> asyncTaskWithReturn(String taskName, long sleepTime) {
        log.info("【异步任务(有返回值)】{} 开始执行,当前线程:{},预计耗时:{}ms",
                taskName, Thread.currentThread().getName(), sleepTime);
        try {
            Thread.sleep(sleepTime);
            String result = taskName + " 执行成功";
            returnnew FutureTask<>(() -> result);
        } catch (InterruptedException e) {
            log.error("【异步任务(有返回值)】{} 执行异常", taskName, e);
            Thread.currentThread().interrupt();
            returnnew FutureTask<>(() -> taskName + " 执行失败");
        }
    }
}
2.3.2 测试接口:验证异步效果

编写Controller层接口,调用异步服务方法,验证异步执行效果。

代码语言:javascript
复制
package com.jam.demo.controller;

import com.jam.demo.service.AsyncService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.Future;

/**
 * 异步测试控制器
 * @author ken
 */
@RestController
@RequestMapping("/async")
@Slf4j
@Tag(name = "异步测试接口", description = "用于测试@Async注解的基础使用")
publicclass AsyncTestController {

    @Autowired
    private AsyncService asyncService;

    /**
     * 测试基础异步方法(无返回值)
     * @param taskName 任务名称
     * @return 接口响应
     */
    @GetMapping("/basic")
    @Operation(summary = "基础异步方法测试", description = "调用无返回值的异步方法,验证异步执行效果")
    public String testBasicAsync(@RequestParam String taskName) {
        log.info("【主线程】开始调用异步方法,当前线程:{}", Thread.currentThread().getName());
        // 调用异步方法
        asyncService.basicAsyncTask(taskName);
        log.info("【主线程】异步方法调用完成,无需等待结果,直接返回响应");
        return"异步任务已触发,可查看日志确认执行情况";
    }

    /**
     * 测试有返回值的异步方法
     * @param taskName 任务名称
     * @param sleepTime 模拟耗时时间(毫秒)
     * @return 任务执行结果
     * @throws Exception 异常
     */
    @GetMapping("/with-return")
    @Operation(summary = "有返回值异步方法测试", description = "调用有返回值的异步方法,通过Future获取执行结果")
    public String testAsyncWithReturn(@RequestParam String taskName, @RequestParam long sleepTime) throws Exception {
        log.info("【主线程】开始调用有返回值的异步方法,当前线程:{}", Thread.currentThread().getName());
        // 调用异步方法,获取Future对象
        Future<String> future = asyncService.asyncTaskWithReturn(taskName, sleepTime);
        log.info("【主线程】异步方法调用完成,可继续执行其他逻辑");
        
        // 模拟主线程其他业务操作
        log.info("【主线程】执行其他业务逻辑...");
        Thread.sleep(1000);
        
        // 通过Future.get()获取异步任务结果(会阻塞,直到任务完成)
        log.info("【主线程】开始获取异步任务结果");
        String result = future.get();
        log.info("【主线程】异步任务结果:{}", result);
        
        return"异步任务执行结果:" + result;
    }
}
2.3.3 测试结果与分析

测试无返回值异步方法(/async/basic?taskName=测试任务1): 日志输出如下(关键观察线程名称和执行顺序):

代码语言:javascript
复制
【主线程】开始调用异步方法,当前线程:http-nio-8080-exec-1
【主线程】异步方法调用完成,无需等待结果,直接返回响应
【异步任务】测试任务1 开始执行,当前线程:SimpleAsyncTaskExecutor-1
【异步任务】测试任务1 执行完毕

结论:主线程(http-nio-8080-exec-1)调用异步方法后,无需等待异步任务完成,直接返回响应;异步任务在独立线程(SimpleAsyncTaskExecutor-1)中执行。

测试有返回值异步方法(/async/with-return?taskName=测试任务2&sleepTime=3000): 日志输出如下:

代码语言:javascript
复制
【主线程】开始调用有返回值的异步方法,当前线程:http-nio-8080-exec-2
【主线程】异步方法调用完成,可继续执行其他逻辑
【主线程】执行其他业务逻辑...
【异步任务(有返回值)】测试任务2 开始执行,当前线程:SimpleAsyncTaskExecutor-2,预计耗时:3000ms
【主线程】开始获取异步任务结果
【异步任务(有返回值)】测试任务2 执行完毕
【主线程】异步任务结果:测试任务2 执行成功

结论:主线程调用异步方法后先执行自身业务逻辑,直到调用future.get()才会阻塞等待异步任务完成;异步任务在独立线程中执行。

三、@Async进阶使用:自定义线程池
3.1 为什么需要自定义线程池?

默认情况下,@Async使用的是Spring提供的SimpleAsyncTaskExecutor,该线程池的核心问题的是:每次执行异步任务都会创建一个新线程,不会复用线程,当异步任务量较大时,会导致系统创建大量线程,引发线程上下文切换频繁、内存占用过高甚至OOM问题。

因此,在生产环境中,必须自定义线程池,统一管理线程的创建、复用、销毁,合理配置线程池参数。

3.2 自定义线程池的3种方式
3.2.1 方式1:通过@Configuration+@Bean创建ThreadPoolTaskExecutor

这是最常用的方式,通过配置类创建ThreadPoolTaskExecutor(Spring封装的线程池,基于JDK的ThreadPoolExecutor),并指定线程池参数。

代码语言:javascript
复制
package com.jam.demo.config;

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;

/**
 * 自定义线程池配置类
 * @author ken
 */
@Configuration
publicclass AsyncThreadPoolConfig {

    /**
     * 自定义线程池:asyncTaskExecutor
     * 核心参数说明:
     * 1. corePoolSize:核心线程数(默认活跃的线程数)
     * 2. maxPoolSize:最大线程数(线程池可创建的最大线程数)
     * 3. queueCapacity:队列容量(核心线程满后,任务放入队列等待)
     * 4. keepAliveSeconds:非核心线程空闲存活时间(超过该时间则销毁)
     * 5. threadNamePrefix:线程名称前缀(便于日志排查)
     * 6. rejectedExecutionHandler:拒绝策略(任务过多时的处理方式)
     * @return 自定义线程池
     */
    @Bean(name = "asyncTaskExecutor")
    public Executor asyncTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数:根据CPU核心数配置(一般为CPU核心数 * 2 + 1)
        executor.setCorePoolSize(5);
        // 最大线程数
        executor.setMaxPoolSize(10);
        // 队列容量:核心线程满后,任务放入队列,队列满后才会创建非核心线程
        executor.setQueueCapacity(25);
        // 非核心线程空闲存活时间:30秒
        executor.setKeepAliveSeconds(30);
        // 线程名称前缀
        executor.setThreadNamePrefix("AsyncTask-");
        // 拒绝策略:当线程池、队列都满时,直接抛出异常(生产环境可根据需求调整)
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        // 初始化线程池(必须调用,否则线程池无法生效)
        executor.initialize();
        return executor;
    }
}
3.2.2 方式2:实现AsyncConfigurer接口

通过实现AsyncConfigurer接口,重写getAsyncExecutor()方法返回自定义线程池,同时可重写getAsyncUncaughtExceptionHandler()方法自定义异步任务异常处理器。

代码语言:javascript
复制
package com.jam.demo.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 实现AsyncConfigurer接口自定义线程池
 * @author ken
 */
@Configuration
@Slf4j
publicclass AsyncConfigurerPoolConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(25);
        executor.setKeepAliveSeconds(30);
        executor.setThreadNamePrefix("AsyncConfigurerTask-");
        // 拒绝策略:丢弃最老的任务,执行新任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
        executor.initialize();
        return executor;
    }

    /**
     * 自定义异步任务异常处理器:处理异步方法中未捕获的异常
     * @return 异常处理器
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) -> {
            log.error("【异步任务异常】方法:{},参数:{},异常信息:{}",
                    method.getName(), params, ex.getMessage(), ex);
        };
    }
}
3.2.3 方式3:使用@Async的value属性指定线程池

当系统中有多个线程池时,可通过@Async("线程池bean名称")指定具体使用哪个线程池。

代码语言:javascript
复制
package com.jam.demo.service.impl;

import com.jam.demo.service.AsyncService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

/**
 * 多线程池场景下的异步服务实现
 * @author ken
 */
@Service
@Slf4j
publicclass MultiPoolAsyncServiceImpl implements AsyncService {

    /**
     * 使用指定线程池:asyncTaskExecutor
     * @param taskName 任务名称
     */
    @Override
    @Async("asyncTaskExecutor")
    public void basicAsyncTask(String taskName) {
        log.info("【异步任务(指定线程池)】{} 开始执行,当前线程:{}", taskName, Thread.currentThread().getName());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            log.error("【异步任务(指定线程池)】{} 执行异常", taskName, e);
            Thread.currentThread().interrupt();
        }
        log.info("【异步任务(指定线程池)】{} 执行完毕", taskName);
    }

    /**
     * 使用默认线程池(AsyncConfigurer配置的线程池)
     * @param taskName 任务名称
     * @param sleepTime 模拟耗时时间(毫秒)
     * @return 任务执行结果
     */
    @Override
    @Async
    public Future<String> asyncTaskWithReturn(String taskName, long sleepTime) {
        log.info("【异步任务(默认线程池)】{} 开始执行,当前线程:{},预计耗时:{}ms",
                taskName, Thread.currentThread().getName(), sleepTime);
        try {
            Thread.sleep(sleepTime);
            String result = taskName + " 执行成功(默认线程池)";
            returnnew FutureTask<>(() -> result);
        } catch (InterruptedException e) {
            log.error("【异步任务(默认线程池)】{} 执行异常", taskName, e);
            Thread.currentThread().interrupt();
            returnnew FutureTask<>(() -> taskName + " 执行失败(默认线程池)");
        }
    }
}
3.3 线程池参数配置最佳实践

线程池参数的配置直接影响系统性能,需根据业务场景合理调整,核心配置原则如下:

参数

配置建议

适用场景

corePoolSize

CPU核心数 * 2 + 1(CPU密集型);CPU核心数 * 10(IO密集型)

CPU密集型:计算任务;IO密集型:数据库查询、文件操作

maxPoolSize

不超过CPU核心数 * 20(避免线程过多导致上下文切换频繁)

高并发场景可适当增大

queueCapacity

核心线程数 * 5 ~ 核心线程数 * 10(避免队列过大导致任务堆积)

任务执行时间短、数量多的场景

keepAliveSeconds

30 ~ 60秒(非核心线程空闲时及时销毁,节省资源)

大多数场景通用

拒绝策略

核心业务:AbortPolicy(抛出异常,便于监控);非核心业务:DiscardOldestPolicy/DiscardPolicy

核心业务需保证任务不丢失;非核心业务可丢弃旧任务

四、@Async底层原理:从注解解析到动态代理

要真正掌握@Async,必须理解其底层实现原理。@Async基于Spring AOP机制,通过动态代理为目标方法创建代理对象,将异步调用逻辑织入代理方法中。

4.1 核心执行流程
4.2 关键组件解析
  1. @EnableAsync:开启异步支持,核心是导入AsyncConfigurationSelector,该类会根据Spring版本选择对应的异步配置类(如ProxyAsyncConfiguration)。
  2. AsyncAnnotationBeanPostProcessor:后置处理器,用于扫描带有@Async注解的方法,为目标类创建动态代理(JDK动态代理或CGLIB代理)。
  3. AsyncTaskExecutor:异步任务执行器,即线程池,是异步调用的核心载体。
  4. AnnotationAsyncExecutionInterceptor:异步方法拦截器,代理对象调用方法时会被该拦截器拦截,负责将任务提交到线程池。
4.3 动态代理机制详解

当我们调用被@Async注解的方法时,实际调用的是代理对象的方法,而非目标对象的原始方法。代理对象的核心逻辑如下:

  1. 拦截目标方法调用;
  2. 解析@Async注解的属性(如指定的线程池名称);
  3. 获取对应的线程池;
  4. 将目标方法的执行逻辑封装为CallableRunnable任务;
  5. 将任务提交到线程池,由线程池中的线程执行;
  6. 主线程直接返回(无返回值)或返回Future对象(有返回值)。
4.4 源码片段解析(关键逻辑)

以下是AnnotationAsyncExecutionInterceptor中拦截方法的核心源码(简化版),清晰展示了异步任务的提交过程:

代码语言:javascript
复制
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
    // 1. 获取目标方法
    Class<?> targetClass = invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null;
    Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
    
    // 2. 解析@Async注解,获取线程池
    AsyncTaskExecutor executor = determineAsyncExecutor(specificMethod);
    if (executor == null) {
        thrownew IllegalStateException("No executor specified and no default executor set");
    }
    
    // 3. 将目标方法封装为Callable任务
    Callable<Object> task = () -> {
        try {
            // 执行目标方法
            Object result = invocation.proceed();
            if (result instanceof Future) {
                return ((Future<?>) result).get();
            }
        } catch (Throwable ex) {
            // 处理异常
            handleError(ex, specificMethod, invocation.getArguments());
        }
        returnnull;
    };
    
    // 4. 提交任务到线程池,返回Future对象
    return doSubmit(task, executor, specificMethod);
}
五、@Async实战进阶:事务处理、异常处理与批量异步
5.1 异步方法与事务的关系

核心结论:@Async注解的方法与事务注解(@Transactional)同时使用时,事务不会生效。原因如下:

  • 事务基于Spring AOP,需要通过代理对象调用才能生效;
  • @Async的动态代理会将方法提交到线程池执行,此时目标方法的调用脱离了事务代理的上下文,事务注解无法被识别。
5.1.1 错误示例(事务不生效)
代码语言:javascript
复制
package com.jam.demo.service.impl;

import com.jam.demo.entity.User;
import com.jam.demo.mapper.UserMapper;
import com.jam.demo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 错误示例:异步方法+事务(事务不生效)
 * @author ken
 */
@Service
@Slf4j
publicclass UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    /**
     * 错误示例:@Async与@Transactional同时使用,事务不生效
     * 原因:异步方法在独立线程执行,脱离了事务代理上下文
     * @param user 用户信息
     */
    @Override
    @Async
    @Transactional(rollbackFor = Exception.class)
    public void asyncSaveUser(User user) {
        userMapper.insert(user);
        // 模拟异常
        int i = 1 / 0;
    }
}
5.1.2 正确示例(事务生效的异步方案)

要实现异步方法的事务控制,需将事务逻辑抽离到独立的服务方法中,由异步方法调用该事务方法(确保事务方法被代理对象调用)。

代码语言:javascript
复制
package com.jam.demo.service.impl;

import com.jam.demo.entity.User;
import com.jam.demo.mapper.UserMapper;
import com.jam.demo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 正确示例:异步方法+事务(事务生效)
 * 方案:将事务逻辑抽离到独立方法,异步方法调用事务方法
 * @author ken
 */
@Service
@Slf4j
publicclass UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    /**
     * 异步方法:仅负责触发异步执行,不包含事务逻辑
     * @param user 用户信息
     */
    @Override
    @Async("asyncTaskExecutor")
    public void asyncSaveUser(User user) {
        log.info("【异步任务】开始执行用户保存,当前线程:{}", Thread.currentThread().getName());
        // 调用事务方法
        doSaveUser(user);
        log.info("【异步任务】用户保存执行完毕");
    }

    /**
     * 事务方法:独立的事务逻辑,由Spring代理对象调用
     * @param user 用户信息
     */
    @Transactional(rollbackFor = Exception.class)
    public void doSaveUser(User user) {
        userMapper.insert(user);
        // 模拟异常,事务会回滚
        int i = 1 / 0;
    }
}
5.2 异步方法的异常处理

异步方法中如果发生未捕获的异常,由于线程是独立的,主线程无法感知,会导致异常丢失。因此,必须配置异常处理机制。

5.2.1 方式1:实现AsyncUncaughtExceptionHandler(全局异常处理)

如3.2.2节所示,通过实现AsyncConfigurer接口的getAsyncUncaughtExceptionHandler()方法,配置全局异步异常处理器,处理所有无返回值异步方法的未捕获异常。

5.2.2 方式2:通过Future获取异常(有返回值方法)

有返回值的异步方法会返回Future对象,调用Future.get()方法时,会将异步方法中的异常抛出,可通过try-catch捕获。

代码语言:javascript
复制
// 示例:捕获有返回值异步方法的异常
@GetMapping("/with-return-exception")
@Operation(summary = "有返回值异步方法异常处理测试")
public String testAsyncWithReturnException() {
    log.info("【主线程】开始调用有返回值的异步方法");
    Future<String> future = asyncService.asyncTaskWithReturn("测试异常任务", 2000);
    try {
        String result = future.get();
        return result;
    } catch (Exception e) {
        log.error("【主线程】捕获异步任务异常", e);
        return "异步任务执行失败:" + e.getMessage();
    }
}
5.2.3 方式3:自定义异常处理器(局部异常处理)

通过@AsyncexceptionHandler属性指定局部异常处理器(需Spring 4.1+版本支持)。

代码语言:javascript
复制
package com.jam.demo.handler;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * 自定义异步异常处理器(局部)
 * @author ken
 */
@Component
@Slf4j
publicclass CustomAsyncExceptionHandler {

    /**
     * 处理异步方法异常
     * @param ex 异常对象
     * @param method 方法对象
     * @param params 方法参数
     */
    public void handleException(Throwable ex, String method, Object... params) {
        log.error("【自定义异步异常】方法:{},参数:{},异常信息:{}", method, params, ex.getMessage(), ex);
    }
}
代码语言:javascript
复制
// 使用局部异常处理器
@Async(exceptionHandler = "customAsyncExceptionHandler")
public void asyncTaskWithCustomExceptionHandler(String taskName) {
    log.info("【异步任务(自定义异常处理器)】{} 开始执行", taskName);
    // 模拟异常
    int i = 1 / 0;
}
5.3 批量异步任务处理

在实际开发中,经常需要批量执行异步任务(如批量发送短信、批量处理数据),此时可通过CompletableFuture实现批量任务的并发执行、结果聚合、异常捕获。

5.3.1 批量异步任务示例(基于CompletableFuture)
代码语言:javascript
复制
package com.jam.demo.service.impl;

import com.jam.demo.service.BatchAsyncService;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.concurrent.CompletableFuture;

/**
 * 批量异步任务服务实现
 * @author ken
 */
@Service
@Slf4j
publicclass BatchAsyncServiceImpl implements BatchAsyncService {

    /**
     * 单个批量任务的异步方法
     * @param taskId 任务ID
     * @return CompletableFuture<String> 任务执行结果
     */
    @Async("asyncTaskExecutor")
    public CompletableFuture<String> batchTask(Integer taskId) {
        log.info("【批量异步任务】任务{} 开始执行,当前线程:{}", taskId, Thread.currentThread().getName());
        try {
            // 模拟耗时操作(如处理单条数据)
            Thread.sleep(1000);
            String result = "任务" + taskId + " 执行成功";
            return CompletableFuture.completedFuture(result);
        } catch (InterruptedException e) {
            log.error("【批量异步任务】任务{} 执行异常", taskId, e);
            Thread.currentThread().interrupt();
            return CompletableFuture.failedFuture(e);
        }
    }

    /**
     * 批量执行异步任务,聚合结果
     * @param taskCount 任务数量
     * @return 所有任务的执行结果
     */
    @Override
    public CompletableFuture<List<String>> executeBatchTasks(Integer taskCount) {
        // 生成任务列表
        List<Integer> taskIds = Lists.newArrayList();
        for (int i = 1; i <= taskCount; i++) {
            taskIds.add(i);
        }

        // 批量提交异步任务,获取CompletableFuture列表
        List<CompletableFuture<String>> futureList = taskIds.stream()
                .map(this::batchTask)
                .toList();

        // 聚合所有任务结果:当所有任务完成后,收集结果
        return CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]))
                .thenApply(v -> futureList.stream()
                        .map(CompletableFuture::join)
                        .toList());
    }
}
5.3.2 测试批量异步任务
代码语言:javascript
复制
@GetMapping("/batch")
@Operation(summary = "批量异步任务测试", description = "批量执行异步任务,聚合所有任务结果")
public CompletableFuture<String> testBatchAsyncTasks(@RequestParam Integer taskCount) {
    log.info("【主线程】开始批量提交异步任务,任务数量:{}", taskCount);
    CompletableFuture<List<String>> batchFuture = batchAsyncService.executeBatchTasks(taskCount);
    return batchFuture.thenApply(results -> {
        log.info("【主线程】所有批量异步任务执行完毕,结果:{}", results);
        return "批量任务执行完成,共" + results.size() + "个任务,结果:" + results;
    });
}
六、@Async常见坑与避坑指南
6.1 坑1:异步方法不生效
6.1.1 常见原因
  1. 未添加@EnableAsync注解;
  2. 异步方法为private修饰(Spring AOP无法拦截private方法);
  3. 异步方法被同一个类中的其他方法调用(内部调用,未经过代理对象);
  4. 自定义线程池未调用initialize()方法(线程池未初始化);
  5. 依赖注入的是目标对象而非代理对象(如使用new关键字创建对象)。
6.1.2 避坑方案
  1. 确保启动类或配置类上添加@EnableAsync
  2. 异步方法必须为public修饰;
  3. 避免内部调用:异步方法和调用方必须在不同的类中;
  4. 自定义线程池时,必须调用executor.initialize()
  5. 依赖注入使用@Autowired@Resource,避免使用new关键字创建对象。
6.1.3 错误示例与正确示例
代码语言:javascript
复制
// 错误示例1:内部调用(异步不生效)
@Service
publicclass AsyncErrorService {
    // 内部调用异步方法,未经过代理对象
    public void callAsyncMethod() {
        asyncMethod();
    }

    @Async
    public void asyncMethod() {
        // 异步逻辑
    }
}

// 错误示例2:private修饰(异步不生效)
@Service
publicclass AsyncErrorService {
    @Async
    private void asyncMethod() {
        // 异步逻辑
    }
}

// 正确示例:不同类调用(异步生效)
@Service
publicclass AsyncCallerService {
    @Autowired
    private AsyncService asyncService;

    // 调用不同类中的异步方法,经过代理对象
    public void callAsyncMethod() {
        asyncService.basicAsyncTask("正确示例任务");
    }
}
6.2 坑2:线程池耗尽
6.2.1 常见原因
  1. 使用默认线程池SimpleAsyncTaskExecutor(每次创建新线程,无上限);
  2. 线程池参数配置不合理(核心线程数、最大线程数过小,队列容量过小);
  3. 异步任务执行时间过长,导致线程被长时间占用;
  4. 任务提交速度超过线程池处理速度,导致任务堆积、线程池满负荷。
6.2.2 避坑方案
  1. 生产环境禁用默认线程池,必须自定义线程池;
  2. 根据业务场景合理配置线程池参数(参考3.3节最佳实践);
  3. 监控异步任务执行时间,优化耗时任务(如拆分大任务、优化SQL);
  4. 配置合理的拒绝策略,避免任务过多时系统崩溃;
  5. 对异步任务进行限流,避免短时间内提交大量任务。
6.3 坑3:事务不生效
6.3.1 常见原因

如5.1节所述,@Async与@Transactional同时使用时,异步方法脱离了事务代理上下文,导致事务不生效。

6.3.2 避坑方案

将事务逻辑抽离到独立的public方法中,由异步方法调用该事务方法(确保事务方法被代理对象调用),具体示例参考5.1.2节。

6.4 坑4:异常丢失
6.4.1 常见原因

无返回值的异步方法中发生未捕获的异常,由于线程独立,主线程无法感知,导致异常丢失。

6.4.2 避坑方案

配置全局或局部异常处理器(参考5.2节),确保所有异步方法的异常都能被捕获和记录。

6.5 坑5:异步方法返回值错误
6.5.1 常见原因

有返回值的异步方法未返回Future或其实现类(如直接返回String、Integer等基本类型),导致无法获取异步执行结果。

6.5.2 避坑方案

有返回值的异步方法必须返回Future或其实现类(如FutureTaskCompletableFuture),通过Future.get()获取执行结果。

代码语言:javascript
复制
// 错误示例:返回值不是Future(无法获取异步结果)
@Async
public String asyncTaskWithWrongReturn(String taskName) {
    return taskName + " 执行成功";
}

// 正确示例:返回Future(可获取异步结果)
@Async
public Future<String> asyncTaskWithCorrectReturn(String taskName) {
    return new FutureTask<>(() -> taskName + " 执行成功");
}
七、@Async实战案例:用户注册异步通知系统
7.1 案例需求

用户注册成功后,需要执行以下3个异步任务:

  1. 发送注册成功短信;
  2. 发送注册成功邮件;
  3. 记录用户注册日志到数据库。

要求:3个任务并发执行,提升注册接口响应速度;确保每个任务的异常都能被捕获;记录日志的任务需要事务支持(确保日志数据入库成功)。

7.2 案例实现
7.2.1 数据库表设计(MySQL)
代码语言:javascript
复制
-- 用户表
CREATETABLE`user` (
`id`bigintNOTNULL AUTO_INCREMENT COMMENT'主键ID',
`username`varchar(50) NOTNULLCOMMENT'用户名',
`phone`varchar(20) NOTNULLCOMMENT'手机号',
`email`varchar(100) NOTNULLCOMMENT'邮箱',
`create_time` datetime NOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',
  PRIMARY KEY (`id`),
UNIQUEKEY`uk_username` (`username`)
) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COMMENT='用户表';

-- 注册日志表
CREATETABLE`user_register_log` (
`id`bigintNOTNULL AUTO_INCREMENT COMMENT'主键ID',
`user_id`bigintNOTNULLCOMMENT'用户ID',
`register_ip`varchar(50) NOTNULLCOMMENT'注册IP',
`log_time` datetime NOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'日志时间',
  PRIMARY KEY (`id`),
KEY`idx_user_id` (`user_id`)
) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COMMENT='用户注册日志表';
7.2.2 实体类
代码语言:javascript
复制
package com.jam.demo.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.time.LocalDateTime;

/**
 * 用户实体类
 * @author ken
 */
@Data
@TableName("user")
publicclass User {
    /**
     * 主键ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 手机号
     */
    private String phone;

    /**
     * 邮箱
     */
    private String email;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;
}
代码语言:javascript
复制
package com.jam.demo.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.time.LocalDateTime;

/**
 * 用户注册日志实体类
 * @author ken
 */
@Data
@TableName("user_register_log")
publicclass UserRegisterLog {
    /**
     * 主键ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 用户ID
     */
    private Long userId;

    /**
     * 注册IP
     */
    private String registerIp;

    /**
     * 日志时间
     */
    private LocalDateTime logTime;
}
7.2.3 Mapper层
代码语言:javascript
复制
package com.jam.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.User;
import org.springframework.stereotype.Repository;

/**
 * 用户Mapper
 * @author ken
 */
@Repository
public interface UserMapper extends BaseMapper<User> {
}
代码语言:javascript
复制
package com.jam.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.UserRegisterLog;
import org.springframework.stereotype.Repository;

/**
 * 注册日志Mapper
 * @author ken
 */
@Repository
public interface UserRegisterLogMapper extends BaseMapper<UserRegisterLog> {
}
7.2.4 服务层
代码语言:javascript
复制
package com.jam.demo.service;

import com.jam.demo.entity.User;
import com.jam.demo.entity.UserRegisterLog;

/**
 * 注册相关服务
 * @author ken
 */
publicinterface RegisterService {
    /**
     * 用户注册(同步方法:保存用户信息)
     * @param user 用户信息
     * @param registerIp 注册IP
     * @return 注册成功的用户ID
     */
    Long userRegister(User user, String registerIp);

    /**
     * 发送注册成功短信(异步方法)
     * @param phone 手机号
     */
    void sendRegisterSms(String phone);

    /**
     * 发送注册成功邮件(异步方法)
     * @param email 邮箱
     */
    void sendRegisterEmail(String email);

    /**
     * 记录注册日志(异步+事务方法)
     * @param userId 用户ID
     * @param registerIp 注册IP
     */
    void recordRegisterLog(Long userId, String registerIp);
}
代码语言:javascript
复制
package com.jam.demo.service.impl;

import com.jam.demo.entity.User;
import com.jam.demo.entity.UserRegisterLog;
import com.jam.demo.mapper.UserMapper;
import com.jam.demo.mapper.UserRegisterLogMapper;
import com.jam.demo.service.RegisterService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;

/**
 * 注册相关服务实现
 * @author ken
 */
@Service
@Slf4j
publicclass RegisterServiceImpl implements RegisterService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserRegisterLogMapper userRegisterLogMapper;

    /**
     * 用户注册(同步方法:保存用户信息)
     * 注:用户注册是核心流程,需同步执行,确保用户信息入库成功后再触发异步任务
     * @param user 用户信息
     * @param registerIp 注册IP
     * @return 注册成功的用户ID
     */
    @Override
    public Long userRegister(User user, String registerIp) {
        log.info("【用户注册】开始保存用户信息,用户名:{}", user.getUsername());
        // 保存用户信息
        user.setCreateTime(LocalDateTime.now());
        userMapper.insert(user);
        Long userId = user.getId();
        log.info("【用户注册】用户信息保存成功,用户ID:{}", userId);

        // 触发3个异步任务(并发执行)
        sendRegisterSms(user.getPhone());
        sendRegisterEmail(user.getEmail());
        recordRegisterLog(userId, registerIp);

        return userId;
    }

    /**
     * 发送注册成功短信(异步方法)
     * @param phone 手机号
     */
    @Override
    @Async("asyncTaskExecutor")
    public void sendRegisterSms(String phone) {
        log.info("【异步任务-发送短信】开始向手机号:{} 发送注册成功短信,当前线程:{}",
                phone, Thread.currentThread().getName());
        // 模拟短信发送耗时
        try {
            Thread.sleep(1500);
        } catch (InterruptedException e) {
            log.error("【异步任务-发送短信】向手机号:{} 发送短信异常", phone, e);
            Thread.currentThread().interrupt();
        }
        log.info("【异步任务-发送短信】向手机号:{} 发送短信成功", phone);
    }

    /**
     * 发送注册成功邮件(异步方法)
     * @param email 邮箱
     */
    @Override
    @Async("asyncTaskExecutor")
    public void sendRegisterEmail(String email) {
        log.info("【异步任务-发送邮件】开始向邮箱:{} 发送注册成功邮件,当前线程:{}",
                email, Thread.currentThread().getName());
        // 模拟邮件发送耗时
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            log.error("【异步任务-发送邮件】向邮箱:{} 发送邮件异常", email, e);
            Thread.currentThread().interrupt();
        }
        log.info("【异步任务-发送邮件】向邮箱:{} 发送邮件成功", email);
    }

    /**
     * 记录注册日志(异步+事务方法)
     * 注:事务逻辑抽离到独立方法,确保事务生效
     * @param userId 用户ID
     * @param registerIp 注册IP
     */
    @Override
    @Async("asyncTaskExecutor")
    public void recordRegisterLog(Long userId, String registerIp) {
        log.info("【异步任务-记录日志】开始记录用户:{} 的注册日志,当前线程:{}",
                userId, Thread.currentThread().getName());
        doRecordRegisterLog(userId, registerIp);
        log.info("【异步任务-记录日志】用户:{} 的注册日志记录成功", userId);
    }

    /**
     * 事务方法:实际执行日志记录逻辑
     * @param userId 用户ID
     * @param registerIp 注册IP
     */
    @Transactional(rollbackFor = Exception.class)
    public void doRecordRegisterLog(Long userId, String registerIp) {
        UserRegisterLog log = new UserRegisterLog();
        log.setUserId(userId);
        log.setRegisterIp(registerIp);
        log.setLogTime(LocalDateTime.now());
        userRegisterLogMapper.insert(log);

        // 模拟异常(测试事务回滚)
        // int i = 1 / 0;
    }
}
7.2.5 控制器层
代码语言:javascript
复制
package com.jam.demo.controller;

import com.jam.demo.entity.User;
import com.jam.demo.service.RegisterService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.util.StringUtils;

/**
 * 用户注册控制器
 * @author ken
 */
@RestController
@RequestMapping("/register")
@Slf4j
@Tag(name = "用户注册接口", description = "用户注册核心接口,包含异步通知功能")
publicclass RegisterController {

    @Autowired
    private RegisterService registerService;

    /**
     * 用户注册接口
     * @param user 用户信息
     * @param registerIp 注册IP
     * @return 注册结果
     */
    @PostMapping
    @Operation(summary = "用户注册", description = "用户注册成功后,异步发送短信、邮件,记录注册日志")
    public String userRegister(@RequestBody User user, @RequestParam String registerIp) {
        // 参数校验
        if (ObjectUtils.isEmpty(user) || !StringUtils.hasText(user.getUsername())
                || !StringUtils.hasText(user.getPhone()) || !StringUtils.hasText(user.getEmail())) {
            return"参数错误:用户名、手机号、邮箱不能为空";
        }
        if (!StringUtils.hasText(registerIp)) {
            return"参数错误:注册IP不能为空";
        }

        log.info("【用户注册接口】开始处理注册请求,用户名:{}", user.getUsername());
        // 执行注册(同步),触发异步任务
        Long userId = registerService.userRegister(user, registerIp);
        log.info("【用户注册接口】注册请求处理完成,用户ID:{},已触发异步通知任务", userId);

        return"注册成功,用户ID:" + userId;
    }
}

7.3 案例测试与结果分析

发送POST请求到/register,请求参数如下:

代码语言:javascript
复制
{
  "username": "test_user",
  "phone": "13800138000",
  "email": "test@example.com"
}

请求参数registerIp127.0.0.1

日志输出如下(关键观察线程名称和执行顺序):

代码语言:javascript
复制
【用户注册接口】开始处理注册请求,用户名:test_user
【用户注册】开始保存用户信息,用户名:test_user
【用户注册】用户信息保存成功,用户ID:1
【异步任务-发送短信】开始向手机号:13800138000 发送注册成功短信,当前线程:AsyncTask-1
【异步任务-发送邮件】开始向邮箱:test@example.com 发送注册成功邮件,当前线程:AsyncTask-2
【异步任务-记录日志】开始记录用户:1 的注册日志,当前线程:AsyncTask-3
【用户注册接口】注册请求处理完成,用户ID:1,已触发异步通知任务
【异步任务-发送短信】向手机号:13800138000 发送短信成功
【异步任务-记录日志】用户:1 的注册日志记录成功
【异步任务-发送邮件】向邮箱:test@example.com 发送邮件成功

结果分析:

  1. 核心流程同步执行:用户信息保存是核心流程,同步执行确保用户注册成功后才触发后续异步任务;
  2. 异步任务并发执行:发送短信、发送邮件、记录日志3个任务在3个独立线程(AsyncTask-1、AsyncTask-2、AsyncTask-3)中并发执行,无需顺序等待;
  3. 接口响应快速:主线程(用户注册接口)在触发异步任务后立即返回响应,无需等待异步任务完成,提升了接口响应速度;
  4. 异常隔离:每个异步任务的异常都被独立捕获,不会影响其他任务和主线程的执行。
八、@Async监控与调优
8.1 异步任务监控

在生产环境中,需要对异步任务的执行状态、线程池状态进行监控,以便及时发现问题。常用监控方案如下:

8.1.1 基于Spring Boot Actuator监控线程池

Spring Boot Actuator提供了线程池监控端点,可通过配置暴露线程池相关指标。

  1. 添加依赖:
代码语言:javascript
复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    <version>3.2.5</version>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
    <version>1.12.5</version>
</dependency>
  1. 配置application.yml:
代码语言:javascript
复制
spring:
  application:
    name:async-demo
management:
endpoints:
    web:
      exposure:
        include:health,info,metrics,threadpool# 暴露线程池监控端点
metrics:
    tags:
      application:${spring.application.name}
    export:
      prometheus:
        enabled:true# 启用Prometheus导出指标
  1. 自定义线程池监控指标:
代码语言:javascript
复制
package com.jam.demo.config;

import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import javax.annotation.PostConstruct;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 线程池监控配置
 * @author ken
 */
@Configuration
publicclass ThreadPoolMonitorConfig {

    @Autowired
    private MeterRegistry meterRegistry;

    @Autowired
    @Qualifier("asyncTaskExecutor")
    private ThreadPoolTaskExecutor asyncTaskExecutor;

    /**
     * 注册线程池监控指标
     */
    @PostConstruct
    public void monitorThreadPool() {
        ThreadPoolExecutor executor = asyncTaskExecutor.getThreadPoolExecutor();
        // 核心线程数
        Gauge.builder("threadpool.core.size", executor, ThreadPoolExecutor::getCorePoolSize)
                .tag("threadpool.name", "asyncTaskExecutor")
                .register(meterRegistry);
        // 活跃线程数
        Gauge.builder("threadpool.active.size", executor, ThreadPoolExecutor::getActiveCount)
                .tag("threadpool.name", "asyncTaskExecutor")
                .register(meterRegistry);
        // 最大线程数
        Gauge.builder("threadpool.max.size", executor, ThreadPoolExecutor::getMaximumPoolSize)
                .tag("threadpool.name", "asyncTaskExecutor")
                .register(meterRegistry);
        // 队列中的任务数
        Gauge.builder("threadpool.queue.size", executor, e -> e.getQueue().size())
                .tag("threadpool.name", "asyncTaskExecutor")
                .register(meterRegistry);
        // 已完成的任务数
        Gauge.builder("threadpool.completed.tasks", executor, ThreadPoolExecutor::getCompletedTaskCount)
                .tag("threadpool.name", "asyncTaskExecutor")
                .register(meterRegistry);
    }
}
  1. 访问监控端点:
  • 查看线程池指标:http://localhost:8080/actuator/metrics/threadpool.active.size?tag=threadpool.name:asyncTaskExecutor
  • 查看Prometheus指标:http://localhost:8080/actuator/prometheus(可结合Grafana可视化展示)
8.1.2 自定义异步任务执行日志

通过AOP切面,记录异步任务的执行时间、参数、结果等信息,便于问题排查。

代码语言:javascript
复制
package com.jam.demo.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 异步任务执行日志切面
 * @author ken
 */
@Aspect
@Component
@Slf4j
publicclass AsyncTaskLogAspect {

    /**
     * 切入点:所有被@Async注解的方法
     */
    @Pointcut("@annotation(org.springframework.scheduling.annotation.Async)")
    public void asyncTaskPointcut() {}

    /**
     * 环绕通知:记录任务执行时间、参数、结果
     * @param joinPoint 连接点
     * @return 任务执行结果
     * @throws Throwable 异常
     */
    @Around("asyncTaskPointcut()")
    public Object aroundAsyncTask(ProceedingJoinPoint joinPoint) throws Throwable {
        // 记录开始时间
        long startTime = System.currentTimeMillis();
        String methodName = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        log.info("【异步任务监控】{} 开始执行,参数:{},当前线程:{}",
                methodName, args, Thread.currentThread().getName());
        try {
            // 执行目标方法
            Object result = joinPoint.proceed();
            // 记录执行时间和结果
            long costTime = System.currentTimeMillis() - startTime;
            log.info("【异步任务监控】{} 执行完成,耗时:{}ms,结果:{}",
                    methodName, costTime, result);
            return result;
        } catch (Throwable e) {
            log.error("【异步任务监控】{} 执行异常,耗时:{}ms",
                    methodName, System.currentTimeMillis() - startTime, e);
            throw e;
        }
    }
}
8.2 异步任务调优
8.2.1 线程池参数动态调优

在生产环境中,线程池参数可能需要根据业务流量动态调整,可通过以下方案实现:

  1. 基于配置中心(如Nacos、Apollo)动态刷新线程池参数;
  2. 提供接口手动调整线程池参数(需做好权限控制)。

示例:基于Nacos动态调整线程池参数

代码语言:javascript
复制
package com.jam.demo.config;

import com.alibaba.nacos.api.config.annotation.NacosConfigListener;
import com.alibaba.nacos.api.config.annotation.NacosValue;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import javax.annotation.Resource;

/**
 * 基于Nacos的线程池动态配置
 * @author ken
 */
@Configuration
publicclass DynamicThreadPoolConfig {

    @Resource
    @Qualifier("asyncTaskExecutor")
    private ThreadPoolTaskExecutor asyncTaskExecutor;

    // 从Nacos获取核心线程数配置
    @NacosValue(value = "${threadpool.async.core-size:5}", autoRefreshed = true)
    privateint corePoolSize;

    // 从Nacos获取最大线程数配置
    @NacosValue(value = "${threadpool.async.max-size:10}", autoRefreshed = true)
    privateint maxPoolSize;

    // 从Nacos获取空闲存活时间配置
    @NacosValue(value = "${threadpool.async.keep-alive-seconds:30}", autoRefreshed = true)
    privateint keepAliveSeconds;

    /**
     * 监听配置变化,动态调整线程池参数
     */
    @NacosConfigListener(dataId = "async-demo-threadpool-config", groupId = "DEFAULT_GROUP")
    public void refreshThreadPoolConfig(String config) {
        // 解析配置(此处简化,实际需解析JSON/Properties格式)
        // 动态调整核心线程数
        asyncTaskExecutor.setCorePoolSize(corePoolSize);
        // 动态调整最大线程数
        asyncTaskExecutor.setMaxPoolSize(maxPoolSize);
        // 动态调整空闲存活时间
        asyncTaskExecutor.setKeepAliveSeconds(keepAliveSeconds);
        // 重新初始化线程池(仅调整核心线程数、最大线程数、空闲存活时间时无需重新初始化)
        asyncTaskExecutor.initialize();
    }
}
8.2.2 任务拆分与合并

对于执行时间过长的大任务,可拆分为多个小任务并发执行,提升执行效率;对于大量小任务,可合并为批次任务执行,减少线程切换开销。

示例:大任务拆分

代码语言:javascript
复制
/**
 * 大任务拆分示例:批量处理1000条数据,拆分为10个小任务,每个任务处理100条
 * @param dataList 待处理数据列表
 * @return 处理结果
 */
@Async("asyncTaskExecutor")
public CompletableFuture<Void> processLargeData(List<String> dataList) {
    // 拆分任务:每100条数据为一个子任务
    List<List<String>> subTasks = Lists.partition(dataList, 100);
    // 并发执行子任务
    List<CompletableFuture<Void>> futureList = subTasks.stream()
            .map(subList -> CompletableFuture.runAsync(() -> processSubTask(subList), asyncTaskExecutor))
            .toList();
    // 等待所有子任务完成
    return CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]));
}

/**
 * 子任务处理逻辑
 * @param subList 子任务数据列表
 */
private void processSubTask(List<String> subList) {
    log.info("【子任务】开始处理 {} 条数据,当前线程:{}", subList.size(), Thread.currentThread().getName());
    // 处理逻辑
    subList.forEach(data -> {
        // 数据处理操作
    });
    log.info("【子任务】数据处理完成");
}
8.2.3 避免异步任务嵌套

异步任务内部尽量不要嵌套调用其他异步任务,否则会导致线程池资源被过度占用,增加系统复杂度和排查难度。若必须嵌套,需严格控制嵌套层级和任务数量。

九、总结与核心要点回顾

@Async注解是Spring框架中实现异步编程的核心工具,其使用简单,但要在生产环境中稳定运行,需掌握以下核心要点:

  1. 基础使用:必须添加@EnableAsync开启异步支持,异步方法需为public修饰,避免内部调用;
  2. 线程池配置:生产环境禁用默认线程池,自定义线程池需合理配置核心参数,调用initialize()初始化;
  3. 事务处理:@Async与@Transactional同时使用时事务不生效,需将事务逻辑抽离到独立方法;
  4. 异常处理:通过AsyncUncaughtExceptionHandlerFuture捕获异常,避免异常丢失;
  5. 避坑指南:重点关注异步不生效、线程池耗尽、事务失效、异常丢失等常见问题;
  6. 监控调优:通过Actuator、自定义日志实现监控,结合配置中心实现动态调优,合理拆分/合并任务提升效率。

通过本文的讲解和实战示例,相信你已全面掌握@Async注解的使用方法和底层逻辑。在实际开发中,需结合业务场景灵活运用异步编程,平衡系统吞吐量和稳定性,让异步成为提升系统性能的利器。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-12-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 果酱带你啃java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 在Java开发领域,异步编程是提升系统吞吐量、优化用户体验的核心手段之一。而Spring框架提供的@Async注解,更是让开发者无需深入了解复杂的线程池原理,就能轻松实现异步调用。但实际开发中,很多同学在使用@Async时会遇到“异步不生效”“线程池耗尽”“事务失效”等问题。本文将从实战出发,结合底层源码,全面拆解@Async注解的使用方法、核心原理、避坑要点,搭配可直接运行的实例,让你真正吃透异步编程的精髓。
    • 一、@Async注解核心认知:什么是异步调用?为什么需要它?
    • 1.1 同步VS异步:本质区别
    • 1.2 异步调用的适用场景
    • 1.3 @Async的核心作用
    • 二、@Async基础使用:从环境搭建到第一个异步程序
    • 2.1 环境依赖准备(Maven)
    • 2.2 启用@Async:@EnableAsync注解
    • 2.3 第一个异步程序:基础使用示例
    • 2.3.1 异步服务接口与实现
    • 2.3.2 测试接口:验证异步效果
    • 2.3.3 测试结果与分析
    • 三、@Async进阶使用:自定义线程池
    • 3.1 为什么需要自定义线程池?
    • 3.2 自定义线程池的3种方式
    • 3.2.1 方式1:通过@Configuration+@Bean创建ThreadPoolTaskExecutor
    • 3.2.2 方式2:实现AsyncConfigurer接口
    • 3.2.3 方式3:使用@Async的value属性指定线程池
    • 3.3 线程池参数配置最佳实践
    • 四、@Async底层原理:从注解解析到动态代理
    • 4.1 核心执行流程
    • 4.2 关键组件解析
    • 4.3 动态代理机制详解
    • 4.4 源码片段解析(关键逻辑)
    • 五、@Async实战进阶:事务处理、异常处理与批量异步
    • 5.1 异步方法与事务的关系
    • 5.1.1 错误示例(事务不生效)
    • 5.1.2 正确示例(事务生效的异步方案)
    • 5.2 异步方法的异常处理
    • 5.2.1 方式1:实现AsyncUncaughtExceptionHandler(全局异常处理)
    • 5.2.2 方式2:通过Future获取异常(有返回值方法)
    • 5.2.3 方式3:自定义异常处理器(局部异常处理)
    • 5.3 批量异步任务处理
    • 5.3.1 批量异步任务示例(基于CompletableFuture)
    • 5.3.2 测试批量异步任务
    • 六、@Async常见坑与避坑指南
    • 6.1 坑1:异步方法不生效
    • 6.1.1 常见原因
    • 6.1.2 避坑方案
    • 6.1.3 错误示例与正确示例
    • 6.2 坑2:线程池耗尽
    • 6.2.1 常见原因
    • 6.2.2 避坑方案
    • 6.3 坑3:事务不生效
    • 6.3.1 常见原因
    • 6.3.2 避坑方案
    • 6.4 坑4:异常丢失
    • 6.4.1 常见原因
    • 6.4.2 避坑方案
    • 6.5 坑5:异步方法返回值错误
    • 6.5.1 常见原因
    • 6.5.2 避坑方案
    • 七、@Async实战案例:用户注册异步通知系统
    • 7.1 案例需求
    • 7.2 案例实现
    • 7.2.1 数据库表设计(MySQL)
    • 7.2.2 实体类
    • 7.2.3 Mapper层
    • 7.2.4 服务层
    • 7.2.5 控制器层
  • 7.3 案例测试与结果分析
    • 八、@Async监控与调优
    • 8.1 异步任务监控
      • 8.1.1 基于Spring Boot Actuator监控线程池
      • 8.1.2 自定义异步任务执行日志
    • 8.2 异步任务调优
      • 8.2.1 线程池参数动态调优
      • 8.2.2 任务拆分与合并
      • 8.2.3 避免异步任务嵌套
    • 九、总结与核心要点回顾
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档