2026/1/1 13:50:54
网站建设
项目流程
外网室内设计网站,网站备案信息查询接口,wordpress百度xml地图,阿里 wordpress插件在Java开发领域#xff0c;异步编程是提升系统吞吐量、优化用户体验的核心手段之一。而Spring框架提供的Async注解#xff0c;更是让开发者无需深入了解复杂的线程池原理#xff0c;就能轻松实现异步调用。但实际开发中#xff0c;很多同学在使用Async时会遇到“异步不生效…在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项目核心依赖如下所有版本采用最新稳定版dependencies !-- Spring Boot核心依赖 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId version3.2.5/version /dependency !-- Spring Boot测试依赖 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId version3.2.5/version scopetest/scope /dependency !-- Lombok简化日志、Getter/Setter等 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId version1.18.30/version scopeprovided/scope /dependency !-- FastJSON2JSON处理 -- dependency groupIdcom.alibaba.fastjson2/groupId artifactIdfastjson2/artifactId version2.0.49/version /dependency !-- Guava集合工具类 -- dependency groupIdcom.google.guava/groupId artifactIdguava/artifactId version33.2.1-jre/version /dependency !-- MyBatis-Plus持久层框架 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency !-- MySQL驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId version8.3.0/version scoperuntime/scope /dependency !-- Swagger3接口文档 -- dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-starter-webmvc-ui/artifactId version2.5.0/version /dependency /dependencies2.2 启用AsyncEnableAsync注解要让Spring识别Async注解必须在配置类或启动类上添加EnableAsync注解该注解的作用是开启Spring的异步方法支持底层会注册异步方法处理器。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注解标记该方法为异步方法。package com.jam.demo.service; /** * 异步服务接口 * author ken */ public interface AsyncService { /** * 基础异步方法无返回值 * param taskName 任务名称 */ void basicAsyncTask(String taskName); /** * 异步方法有返回值返回Future * param taskName 任务名称 * param sleepTime 模拟耗时时间毫秒 * return 任务执行结果 */ FutureString asyncTaskWithReturn(String taskName, long sleepTime); }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 public class 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 FutureString asyncTaskWithReturn(String taskName, long sleepTime) { log.info(【异步任务有返回值】{} 开始执行当前线程{}预计耗时{}ms, taskName, Thread.currentThread().getName(), sleepTime); try { Thread.sleep(sleepTime); String result taskName 执行成功; return new FutureTask(() - result); } catch (InterruptedException e) { log.error(【异步任务有返回值】{} 执行异常, taskName, e); Thread.currentThread().interrupt(); return new FutureTask(() - taskName 执行失败); } } }2.3.2 测试接口验证异步效果编写Controller层接口调用异步服务方法验证异步执行效果。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注解的基础使用) public class 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对象 FutureString 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 日志输出如下关键观察线程名称和执行顺序【主线程】开始调用异步方法当前线程http-nio-8080-exec-1 【主线程】异步方法调用完成无需等待结果直接返回响应 【异步任务】测试任务1 开始执行当前线程SimpleAsyncTaskExecutor-1 【异步任务】测试任务1 执行完毕结论主线程http-nio-8080-exec-1调用异步方法后无需等待异步任务完成直接返回响应异步任务在独立线程SimpleAsyncTaskExecutor-1中执行。测试有返回值异步方法/async/with-return?taskName测试任务2sleepTime3000 日志输出如下【主线程】开始调用有返回值的异步方法当前线程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通过ConfigurationBean创建ThreadPoolTaskExecutor这是最常用的方式通过配置类创建ThreadPoolTaskExecutorSpring封装的线程池基于JDK的ThreadPoolExecutor并指定线程池参数。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 public class 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()方法自定义异步任务异常处理器。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 public class 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名称)指定具体使用哪个线程池。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 public class 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 FutureString asyncTaskWithReturn(String taskName, long sleepTime) { log.info(【异步任务默认线程池】{} 开始执行当前线程{}预计耗时{}ms, taskName, Thread.currentThread().getName(), sleepTime); try { Thread.sleep(sleepTime); String result taskName 执行成功默认线程池; return new FutureTask(() - result); } catch (InterruptedException e) { log.error(【异步任务默认线程池】{} 执行异常, taskName, e); Thread.currentThread().interrupt(); return new FutureTask(() - taskName 执行失败默认线程池); } } }3.3 线程池参数配置最佳实践线程池参数的配置直接影响系统性能需根据业务场景合理调整核心配置原则如下参数配置建议适用场景corePoolSizeCPU核心数 * 2 1CPU密集型CPU核心数 * 10IO密集型CPU密集型计算任务IO密集型数据库查询、文件操作maxPoolSize不超过CPU核心数 * 20避免线程过多导致上下文切换频繁高并发场景可适当增大queueCapacity核心线程数 * 5 ~ 核心线程数 * 10避免队列过大导致任务堆积任务执行时间短、数量多的场景keepAliveSeconds30 ~ 60秒非核心线程空闲时及时销毁节省资源大多数场景通用拒绝策略核心业务AbortPolicy抛出异常便于监控非核心业务DiscardOldestPolicy/DiscardPolicy核心业务需保证任务不丢失非核心业务可丢弃旧任务四、Async底层原理从注解解析到动态代理要真正掌握Async必须理解其底层实现原理。Async基于Spring AOP机制通过动态代理为目标方法创建代理对象将异步调用逻辑织入代理方法中。4.1 核心执行流程4.2 关键组件解析EnableAsync开启异步支持核心是导入AsyncConfigurationSelector该类会根据Spring版本选择对应的异步配置类如ProxyAsyncConfiguration。AsyncAnnotationBeanPostProcessor后置处理器用于扫描带有Async注解的方法为目标类创建动态代理JDK动态代理或CGLIB代理。AsyncTaskExecutor异步任务执行器即线程池是异步调用的核心载体。AnnotationAsyncExecutionInterceptor异步方法拦截器代理对象调用方法时会被该拦截器拦截负责将任务提交到线程池。4.3 动态代理机制详解当我们调用被Async注解的方法时实际调用的是代理对象的方法而非目标对象的原始方法。代理对象的核心逻辑如下拦截目标方法调用解析Async注解的属性如指定的线程池名称获取对应的线程池将目标方法的执行逻辑封装为Callable或Runnable任务将任务提交到线程池由线程池中的线程执行主线程直接返回无返回值或返回Future对象有返回值。4.4 源码片段解析关键逻辑以下是AnnotationAsyncExecutionInterceptor中拦截方法的核心源码简化版清晰展示了异步任务的提交过程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) { throw new IllegalStateException(No executor specified and no default executor set); } // 3. 将目标方法封装为Callable任务 CallableObject task () - { try { // 执行目标方法 Object result invocation.proceed(); if (result instanceof Future) { return ((Future?) result).get(); } } catch (Throwable ex) { // 处理异常 handleError(ex, specificMethod, invocation.getArguments()); } return null; }; // 4. 提交任务到线程池返回Future对象 return doSubmit(task, executor, specificMethod); }五、Async实战进阶事务处理、异常处理与批量异步5.1 异步方法与事务的关系核心结论Async注解的方法与事务注解Transactional同时使用时事务不会生效。原因如下事务基于Spring AOP需要通过代理对象调用才能生效Async的动态代理会将方法提交到线程池执行此时目标方法的调用脱离了事务代理的上下文事务注解无法被识别。5.1.1 错误示例事务不生效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 public class 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 正确示例事务生效的异步方案要实现异步方法的事务控制需将事务逻辑抽离到独立的服务方法中由异步方法调用该事务方法确保事务方法被代理对象调用。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 public class 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捕获。// 示例捕获有返回值异步方法的异常 GetMapping(/with-return-exception) Operation(summary 有返回值异步方法异常处理测试) public String testAsyncWithReturnException() { log.info(【主线程】开始调用有返回值的异步方法); FutureString future asyncService.asyncTaskWithReturn(测试异常任务, 2000); try { String result future.get(); return result; } catch (Exception e) { log.error(【主线程】捕获异步任务异常, e); return 异步任务执行失败 e.getMessage(); } }5.2.3 方式3自定义异常处理器局部异常处理通过Async的exceptionHandler属性指定局部异常处理器需Spring 4.1版本支持。package com.jam.demo.handler; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; /** * 自定义异步异常处理器局部 * author ken */ Component Slf4j public class CustomAsyncExceptionHandler { /** * 处理异步方法异常 * param ex 异常对象 * param method 方法对象 * param params 方法参数 */ public void handleException(Throwable ex, String method, Object... params) { log.error(【自定义异步异常】方法{}参数{}异常信息{}, method, params, ex.getMessage(), ex); } }// 使用局部异常处理器 Async(exceptionHandler customAsyncExceptionHandler) public void asyncTaskWithCustomExceptionHandler(String taskName) { log.info(【异步任务自定义异常处理器】{} 开始执行, taskName); // 模拟异常 int i 1 / 0; }5.3 批量异步任务处理在实际开发中经常需要批量执行异步任务如批量发送短信、批量处理数据此时可通过CompletableFuture实现批量任务的并发执行、结果聚合、异常捕获。5.3.1 批量异步任务示例基于CompletableFuturepackage 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 public class BatchAsyncServiceImpl implements BatchAsyncService { /** * 单个批量任务的异步方法 * param taskId 任务ID * return CompletableFutureString 任务执行结果 */ Async(asyncTaskExecutor) public CompletableFutureString 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 CompletableFutureListString executeBatchTasks(Integer taskCount) { // 生成任务列表 ListInteger taskIds Lists.newArrayList(); for (int i 1; i taskCount; i) { taskIds.add(i); } // 批量提交异步任务获取CompletableFuture列表 ListCompletableFutureString 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 测试批量异步任务GetMapping(/batch) Operation(summary 批量异步任务测试, description 批量执行异步任务聚合所有任务结果) public CompletableFutureString testBatchAsyncTasks(RequestParam Integer taskCount) { log.info(【主线程】开始批量提交异步任务任务数量{}, taskCount); CompletableFutureListString batchFuture batchAsyncService.executeBatchTasks(taskCount); return batchFuture.thenApply(results - { log.info(【主线程】所有批量异步任务执行完毕结果{}, results); return 批量任务执行完成共 results.size() 个任务结果 results; }); }六、Async常见坑与避坑指南6.1 坑1异步方法不生效6.1.1 常见原因未添加EnableAsync注解异步方法为private修饰Spring AOP无法拦截private方法异步方法被同一个类中的其他方法调用内部调用未经过代理对象自定义线程池未调用initialize()方法线程池未初始化依赖注入的是目标对象而非代理对象如使用new关键字创建对象。6.1.2 避坑方案确保启动类或配置类上添加EnableAsync异步方法必须为public修饰避免内部调用异步方法和调用方必须在不同的类中自定义线程池时必须调用executor.initialize()依赖注入使用Autowired或Resource避免使用new关键字创建对象。6.1.3 错误示例与正确示例// 错误示例1内部调用异步不生效 Service public class AsyncErrorService { // 内部调用异步方法未经过代理对象 public void callAsyncMethod() { asyncMethod(); } Async public void asyncMethod() { // 异步逻辑 } } // 错误示例2private修饰异步不生效 Service public class AsyncErrorService { Async private void asyncMethod() { // 异步逻辑 } } // 正确示例不同类调用异步生效 Service public class AsyncCallerService { Autowired private AsyncService asyncService; // 调用不同类中的异步方法经过代理对象 public void callAsyncMethod() { asyncService.basicAsyncTask(正确示例任务); } }6.2 坑2线程池耗尽6.2.1 常见原因使用默认线程池SimpleAsyncTaskExecutor每次创建新线程无上限线程池参数配置不合理核心线程数、最大线程数过小队列容量过小异步任务执行时间过长导致线程被长时间占用任务提交速度超过线程池处理速度导致任务堆积、线程池满负荷。6.2.2 避坑方案生产环境禁用默认线程池必须自定义线程池根据业务场景合理配置线程池参数参考3.3节最佳实践监控异步任务执行时间优化耗时任务如拆分大任务、优化SQL配置合理的拒绝策略避免任务过多时系统崩溃对异步任务进行限流避免短时间内提交大量任务。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或其实现类如FutureTask、CompletableFuture通过Future.get()获取执行结果。// 错误示例返回值不是Future无法获取异步结果 Async public String asyncTaskWithWrongReturn(String taskName) { return taskName 执行成功; } // 正确示例返回Future可获取异步结果 Async public FutureString asyncTaskWithCorrectReturn(String taskName) { return new FutureTask(() - taskName 执行成功); }七、Async实战案例用户注册异步通知系统7.1 案例需求用户注册成功后需要执行以下3个异步任务发送注册成功短信发送注册成功邮件记录用户注册日志到数据库。要求3个任务并发执行提升注册接口响应速度确保每个任务的异常都能被捕获记录日志的任务需要事务支持确保日志数据入库成功。7.2 案例实现7.2.1 数据库表设计MySQL-- 用户表 CREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键ID, username varchar(50) NOT NULL COMMENT 用户名, phone varchar(20) NOT NULL COMMENT 手机号, email varchar(100) NOT NULL COMMENT 邮箱, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; -- 注册日志表 CREATE TABLE user_register_log ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键ID, user_id bigint NOT NULL COMMENT 用户ID, register_ip varchar(50) NOT NULL COMMENT 注册IP, log_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 日志时间, PRIMARY KEY (id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户注册日志表;7.2.2 实体类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) public class User { /** * 主键ID */ TableId(type IdType.AUTO) private Long id; /** * 用户名 */ private String username; /** * 手机号 */ private String phone; /** * 邮箱 */ private String email; /** * 创建时间 */ private LocalDateTime createTime; }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) public class UserRegisterLog { /** * 主键ID */ TableId(type IdType.AUTO) private Long id; /** * 用户ID */ private Long userId; /** * 注册IP */ private String registerIp; /** * 日志时间 */ private LocalDateTime logTime; }7.2.3 Mapper层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 BaseMapperUser { }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 BaseMapperUserRegisterLog { }7.2.4 服务层package com.jam.demo.service; import com.jam.demo.entity.User; import com.jam.demo.entity.UserRegisterLog; /** * 注册相关服务 * author ken */ public interface 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); }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 public class 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 控制器层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 用户注册核心接口包含异步通知功能) public class 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请求参数如下{ username: test_user, phone: 13800138000, email: testexample.com }请求参数registerIp127.0.0.1日志输出如下关键观察线程名称和执行顺序【用户注册接口】开始处理注册请求用户名test_user 【用户注册】开始保存用户信息用户名test_user 【用户注册】用户信息保存成功用户ID1 【异步任务-发送短信】开始向手机号13800138000 发送注册成功短信当前线程AsyncTask-1 【异步任务-发送邮件】开始向邮箱testexample.com 发送注册成功邮件当前线程AsyncTask-2 【异步任务-记录日志】开始记录用户1 的注册日志当前线程AsyncTask-3 【用户注册接口】注册请求处理完成用户ID1已触发异步通知任务 【异步任务-发送短信】向手机号13800138000 发送短信成功 【异步任务-记录日志】用户1 的注册日志记录成功 【异步任务-发送邮件】向邮箱testexample.com 发送邮件成功结果分析核心流程同步执行用户信息保存是核心流程同步执行确保用户注册成功后才触发后续异步任务异步任务并发执行发送短信、发送邮件、记录日志3个任务在3个独立线程AsyncTask-1、AsyncTask-2、AsyncTask-3中并发执行无需顺序等待接口响应快速主线程用户注册接口在触发异步任务后立即返回响应无需等待异步任务完成提升了接口响应速度异常隔离每个异步任务的异常都被独立捕获不会影响其他任务和主线程的执行。八、Async监控与调优8.1 异步任务监控在生产环境中需要对异步任务的执行状态、线程池状态进行监控以便及时发现问题。常用监控方案如下8.1.1 基于Spring Boot Actuator监控线程池Spring Boot Actuator提供了线程池监控端点可通过配置暴露线程池相关指标。添加依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-actuator/artifactId version3.2.5/version /dependency dependency groupIdio.micrometer/groupId artifactIdmicrometer-registry-prometheus/artifactId version1.12.5/version /dependency配置application.ymlspring: application: name: async-demo management: endpoints: web: exposure: include: health,info,metrics,threadpool # 暴露线程池监控端点 metrics: tags: application: ${spring.application.name} export: prometheus: enabled: true # 启用Prometheus导出指标自定义线程池监控指标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 public class 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); } }访问监控端点查看线程池指标http://localhost:8080/actuator/metrics/threadpool.active.size?tagthreadpool.name:asyncTaskExecutor查看Prometheus指标http://localhost:8080/actuator/prometheus可结合Grafana可视化展示8.1.2 自定义异步任务执行日志通过AOP切面记录异步任务的执行时间、参数、结果等信息便于问题排查。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 public class 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 线程池参数动态调优在生产环境中线程池参数可能需要根据业务流量动态调整可通过以下方案实现基于配置中心如Nacos、Apollo动态刷新线程池参数提供接口手动调整线程池参数需做好权限控制。示例基于Nacos动态调整线程池参数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 public class DynamicThreadPoolConfig { Resource Qualifier(asyncTaskExecutor) private ThreadPoolTaskExecutor asyncTaskExecutor; // 从Nacos获取核心线程数配置 NacosValue(value ${threadpool.async.core-size:5}, autoRefreshed true) private int corePoolSize; // 从Nacos获取最大线程数配置 NacosValue(value ${threadpool.async.max-size:10}, autoRefreshed true) private int maxPoolSize; // 从Nacos获取空闲存活时间配置 NacosValue(value ${threadpool.async.keep-alive-seconds:30}, autoRefreshed true) private int 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 任务拆分与合并对于执行时间过长的大任务可拆分为多个小任务并发执行提升执行效率对于大量小任务可合并为批次任务执行减少线程切换开销。示例大任务拆分/** * 大任务拆分示例批量处理1000条数据拆分为10个小任务每个任务处理100条 * param dataList 待处理数据列表 * return 处理结果 */ Async(asyncTaskExecutor) public CompletableFutureVoid processLargeData(ListString dataList) { // 拆分任务每100条数据为一个子任务 ListListString subTasks Lists.partition(dataList, 100); // 并发执行子任务 ListCompletableFutureVoid futureList subTasks.stream() .map(subList - CompletableFuture.runAsync(() - processSubTask(subList), asyncTaskExecutor)) .toList(); // 等待所有子任务完成 return CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])); } /** * 子任务处理逻辑 * param subList 子任务数据列表 */ private void processSubTask(ListString subList) { log.info(【子任务】开始处理 {} 条数据当前线程{}, subList.size(), Thread.currentThread().getName()); // 处理逻辑 subList.forEach(data - { // 数据处理操作 }); log.info(【子任务】数据处理完成); }8.2.3 避免异步任务嵌套异步任务内部尽量不要嵌套调用其他异步任务否则会导致线程池资源被过度占用增加系统复杂度和排查难度。若必须嵌套需严格控制嵌套层级和任务数量。九、总结与核心要点回顾Async注解是Spring框架中实现异步编程的核心工具其使用简单但要在生产环境中稳定运行需掌握以下核心要点基础使用必须添加EnableAsync开启异步支持异步方法需为public修饰避免内部调用线程池配置生产环境禁用默认线程池自定义线程池需合理配置核心参数调用initialize()初始化事务处理Async与Transactional同时使用时事务不生效需将事务逻辑抽离到独立方法异常处理通过AsyncUncaughtExceptionHandler或Future捕获异常避免异常丢失避坑指南重点关注异步不生效、线程池耗尽、事务失效、异常丢失等常见问题监控调优通过Actuator、自定义日志实现监控结合配置中心实现动态调优合理拆分/合并任务提升效率。通过本文的讲解和实战示例相信你已全面掌握Async注解的使用方法和底层逻辑。在实际开发中需结合业务场景灵活运用异步编程平衡系统吞吐量和稳定性让异步成为提升系统性能的利器。