Spring Boot @Async 注解深度指南
Spring Boot @Async 注解深度指南
一、核心使用要点
-
启用异步支持
- 必须在启动类或配置类添加
@EnableAsync
,否则异步不生效。
@SpringBootApplication @EnableAsync public class Application { ... }
- 必须在启动类或配置类添加
-
线程池配置
- 默认问题:Spring 默认使用
SimpleAsyncTaskExecutor
(每次新建线程),生产环境需自定义线程池。 - 推荐配置:通过
ThreadPoolTaskExecutor
定义核心参数(核心线程数、队列容量等):@Configuration @EnableAsync public class AsyncConfig implements AsyncConfigurer {@Overridepublic Executor getAsyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(5);executor.setMaxPoolSize(10);executor.setQueueCapacity(100);executor.setThreadNamePrefix("Async-");executor.initialize(); // 必须初始化return executor;} }
- 默认问题:Spring 默认使用
-
方法调用限制
- 同类调用失效:禁止在同一个类中直接调用
@Async
方法(如this.asyncMethod()
),需通过代理对象调用。 - 解决方案:将异步方法拆分到独立类中,通过依赖注入调用:
@Service public class ServiceA {@Autowiredprivate ServiceB serviceB; // 异步方法在 ServiceB 中定义public void callAsync() {serviceB.asyncMethod(); // 通过代理对象调用} }
- 同类调用失效:禁止在同一个类中直接调用
二、常见失效场景及解决方案
-
方法修饰符错误
- 限制:
@Async
仅对public
方法生效,private
/static
/final
方法无效。
- 限制:
-
事务管理冲突
- 问题:异步方法默认不继承事务上下文,
@Transactional
可能失效。 - 解决方案:
- 在异步方法内部显式管理事务。
- 使用分布式事务框架(如 Seata)。
- 问题:异步方法默认不继承事务上下文,
-
异常处理缺失
- 默认行为:异步方法抛出的异常不会传播到调用线程,需通过
Future
或AsyncUncaughtExceptionHandler
捕获。 - 全局异常处理配置:
@Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return (ex, method, params) -> log.error("异步方法 {} 异常: {}", method.getName(), ex.getMessage()); }
- 默认行为:异步方法抛出的异常不会传播到调用线程,需通过
三、进阶注意事项
-
线程上下文传递
- 问题:异步线程默认不继承主线程的上下文(如 MDC 日志跟踪、SecurityContext)。
- 解决方案:通过
TaskDecorator
装饰任务,手动传递上下文:executor.setTaskDecorator(task -> {Map<String, String> context = MDC.getCopyOfContextMap(); // 获取主线程上下文return () -> {MDC.setContextMap(context); // 设置到异步线程task.run();MDC.clear();}; });
-
与定时任务结合
- 风险:在
@Scheduled
或@XxlJob
标注的方法上直接使用@Async
,可能导致调度平台无法感知任务结果。 - 建议:仅在任务内部耗时操作中使用异步,而非整个方法。
- 风险:在
-
资源释放
- 线程池关闭:Spring 管理的
ThreadPoolTaskExecutor
会在应用关闭时自动调用shutdown()
,但需设置等待任务完成:executor.setWaitForTasksToCompleteOnShutdown(true); // 等待任务完成 executor.setAwaitTerminationSeconds(60); // 最长等待时间
- 线程池关闭:Spring 管理的
四、实际案例解析
案例 1:基础异步调用(无返回值)
场景:执行耗时任务(如日志记录、消息推送)时不阻塞主线程。
代码实现:
@Service
public class NotificationService {@Asyncpublic void sendEmail(String content) {System.out.println("异步发送邮件中,线程:" + Thread.currentThread().getName());// 模拟耗时操作try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }System.out.println("邮件发送完成!");}
}@RestController
public class UserController {@Autowiredprivate NotificationService notificationService;@PostMapping("/register")public String registerUser() {notificationService.sendEmail("欢迎注册!"); // 异步执行return "注册成功,邮件发送中..."; // 主线程立即返回}
}
关键点:
- 方法需标记为
public
,且类需被 Spring 管理(如@Service
); - 主线程调用后立即返回,任务由
SimpleAsyncTaskExecutor
默认线程池执行。
案例 2:带返回值的异步任务
场景:异步执行任务并获取结果(如批量数据处理)。
代码实现:
@Service
public class DataProcessService {@Asyncpublic CompletableFuture<List<String>> processData(List<String> data) {System.out.println("异步处理数据,线程:" + Thread.currentThread().getName());// 模拟耗时处理List<String> result = data.stream().map(String::toUpperCase).collect(Collectors.toList());return CompletableFuture.completedFuture(result);}
}@RestController
public class DataController {@Autowiredprivate DataProcessService dataProcessService;@GetMapping("/process")public CompletableFuture<String> process() {return dataProcessService.processData(Arrays.asList("a", "b", "c")).thenApply(result -> "处理结果:" + result);}
}
关键点:
- 返回值需用
CompletableFuture
或Future
包装; - 调用方通过
thenApply
或get()
获取结果(注意阻塞风险)。
案例 3:自定义线程池配置
场景:优化线程资源,避免默认线程池的性能问题。
配置类: 注意:这里 使用 @Bean(‘线程池名称’) 注解 或 实现 AsyncConfigurer 可任选其一 也可都实现, 实现AsyncConfigurer 代表将 将spring默认线程池替换为 当前线程池(默认线程池存在问题:“Spring 默认使用 SimpleAsyncTaskExecutor(每次新建线程),生产环境需自定义线程池。”),使用 @Bean() 则需要给当前线程池的指定bean名称,在使用 @Async(‘线程池名称’)注解时,就需要指定线程池的名称了,当然这个案例中这里不是必须的,当@Async不指定线程池名称时,则使用的时是默认线程池,在这个案例中,默认的线程池也就是 ‘customExecutor’。
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {@Bean("customExecutor")public Executor taskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(5);executor.setMaxPoolSize(10);executor.setQueueCapacity(100);executor.setThreadNamePrefix("Custom-Async-");executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.initialize();return executor;}
}
使用示例:
@Service
public class ReportService {@Async("customExecutor") // 指定线程池public void generateReport() {System.out.println("生成报表中,线程:" + Thread.currentThread().getName());}
}
关键点:
- 通过
@Async("beanName")
指定线程池; - 拒绝策略推荐
CallerRunsPolicy
避免任务丢失。
案例 4:异步事务管理
场景:异步方法中操作数据库并保证事务一致性。
代码实现:
@Service
public class OrderService {@Autowiredprivate OrderRepository orderRepository;@Async@Transactional(propagation = Propagation.REQUIRES_NEW)public void asyncCreateOrder(Order order) {orderRepository.save(order); // 事务独立提交if (order.getAmount() < 0) {throw new RuntimeException("金额异常"); // 触发回滚}}
}
关键点:
- 异步方法需添加
@Transactional
并指定传播行为(如REQUIRES_NEW
); - 主线程事务与异步线程事务相互隔离。
案例 5:全局异常处理
场景:捕获异步方法中的未处理异常。
(1)默认行为与风险
1. 无返回值方法异常静默丢弃
例如:
@Async
public void asyncTask() {throw new RuntimeException("异步异常"); // 无日志、无处理
}
异常会被 Spring 默认的 SimpleAsyncUncaughtExceptionHandler
处理,仅打印 ERROR
级别日志,但无具体堆栈信息。
2. 调试困难
异步线程的异常不会传播到调用线程,若未记录日志,可能无法发现潜在问题。
(2)最佳实践
1. 无返回值处理异常:
通过自定义异常处理器,统一记录日志或发送告警:
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {@Overridepublic AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return (ex, method, params) -> {// 记录日志或发送告警log.error("异步方法 {} 异常,参数: {}", method.getName(), Arrays.toString(params), ex);// 发送邮件/企业微信通知(可选)};}
}
使用示例:
@Service
public class NotificationService {@Asyncpublic void sendEmail() {throw new RuntimeException("邮件发送失败");}
}
- 触发场景:调用
sendEmail()
时,异常会被AsyncUncaughtExceptionHandler
捕获并记录日志。
2. 结合返回值处理异常:
1. 通过 Future.get()
捕获异常
使用 CompletableFuture
包装返回值,调用 get()
时显式捕获异常。
@Service
public class DataService {@Asyncpublic CompletableFuture<String> processData() {return CompletableFuture.supplyAsync(() -> {if (error) throw new RuntimeException("数据处理异常");return "处理结果";});}
}@RestController
public class DataController {@Autowiredprivate DataService dataService;@GetMapping("/process")public String process() {try {return dataService.processData().get();} catch (InterruptedException | ExecutionException e) {return "处理失败: " + e.getCause().getMessage();}}
}
2. 通过 exceptionally()
链式处理
利用 CompletableFuture
的链式异常处理:
@Async
public CompletableFuture<String> asyncTask() {return CompletableFuture.supplyAsync(() -> {throw new RuntimeException("任务失败");}).exceptionally(ex -> {log.error("任务异常", ex);return "默认值";});
}
注意事项
- 阻塞风险:
Future.get()
会阻塞主线程,需结合超时机制(如get(5, TimeUnit.SECONDS)
)。 - 线程池隔离:建议为耗时任务配置独立线程池,避免核心业务线程池被阻塞。
总结与最佳实践
场景 | 策略 | 适用场景 |
---|---|---|
无返回值 + 无需关注结果 | 必须实现 getAsyncUncaughtExceptionHandler | 日志记录、消息推送等非关键任务 |
无返回值 + 需关注结果 | 重构为有返回值方法,或实现异常处理器 | 数据同步、状态更新等关键任务 |
有返回值 | 优先通过 Future 或 CompletableFuture 处理异常 | 批量处理、复杂计算等需返回结果的任务 |
最佳实践:
- 混合使用:对关键任务使用
CompletableFuture
返回值,非关键任务用void
+ 全局处理器。 - 监控告警:在
AsyncUncaughtExceptionHandler
中集成 Sentry 或 Prometheus 监控。 - 事务拆分:异步方法涉及数据库操作时,确保事务边界清晰(如拆分到独立服务)。
- 线程池隔离:为耗时任务配置独立线程池,避免核心业务线程池被阻塞。
**案例 6:定时任务异步化
- 场景:XXL-JOB 任务异步执行,避免阻塞调度线程。
- 代码示例:
@Component @XxlJob("generateTask") public class TaskJob {@Asyncpublic void generateTask(String param) {// 异步执行耗时任务} }
- 风险:调度平台无法获取执行结果,需通过日志或回调机制跟踪状态。
五、性能优化与监控
-
参数调优
- 核心公式:
- 核心线程数 ≈ CPU 核心数 × 2
- 队列容量根据任务平均耗时调整(避免 OOM)。
- 核心公式:
-
拒绝策略选择
- 生产推荐:使用
CallerRunsPolicy
(由调用线程执行任务),避免任务丢失:executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
- 生产推荐:使用
-
监控指标
- 关键指标:活跃线程数 (
getActiveCount()
)、队列大小 (getQueue().size()
)、已完成任务数 (getCompletedTaskCount()
)。
- 关键指标:活跃线程数 (
总结与最佳实践
场景 | 技术方案 |
---|---|
简单异步任务 | 无返回值方法 + 默认线程池 |
结果依赖任务 | CompletableFuture 包装返回值 |
高并发优化 | 自定义 ThreadPoolTaskExecutor |
数据库事务 | @Transactional + 独立传播行为 |
异常处理 | AsyncUncaughtExceptionHandler |
注意事项:
- 避免同类内调用
@Async
方法(需通过代理对象调用); - 监控线程池状态(活跃线程数、队列堆积)防止资源耗尽;
- 异步方法中谨慎使用
ThreadLocal
,需通过TaskDecorator
传递上下文。
相关文章:
Spring Boot @Async 注解深度指南
Spring Boot Async 注解深度指南 一、核心使用要点 启用异步支持 必须在启动类或配置类添加 EnableAsync,否则异步不生效。 SpringBootApplication EnableAsync public class Application { ... }线程池配置 默认问题:Spring 默认使用 SimpleAsyncTaskEx…...

windows设置暂停更新时长
windows设置暂停更新时长 win11与win10修改注册表操作一致 ,系统界面不同 1.打开注册表 2.在以下路径 \HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings 右键新建 DWORD 32位值,名称为FlightSettingsMaxPauseDays 根据需求填写数…...

Orange 开源项目 - 集成百度智能云-千帆大模型
1 集成百度智能云-千帆大模型 百度智能云-千帆ModelBuilder百度智能云千帆大模型服务与开发平台ModelBuilder(以下简称千帆ModelBuilder)是面向企业开发者的一站式大模型开发及服务运行平台。千帆ModelBuilder不仅提供了包括文心一言底层模型和第三方开源…...

特斯拉 FSD 算法深度剖析:软件层面全解读
一、引言 特斯拉的 FSD(Full Self-Driving)系统作为自动驾驶领域的前沿成果,其软件层面的算法设计至关重要。本文将从软件的角度,深入探讨特斯拉 FSD 所采用的算法,包括感知、规划、控制等多个方面,以期为…...
2025/2/17--2/23学习笔记(week1)_C语言
1 整数的存储 只有整数才有原码,反码,补码,原码取反加一(除了符号位)得到补码。补码的补码会变成原码。 在任何位运算里,都是操作的补码,因为整数在内存里都是以补码存储的 2 移位运算符 移位…...

数据结构:二叉树的数组结构以及堆的实现详解
目录 一.树与二叉树 1.树的概念与相关术语: 2.二叉树: (1)定义: (2)特殊的二叉树: (3)完全二叉树 (4)二叉树的存储结构&#x…...

AWS S3 如何设置公开访问权限?
1.让整个bucket都有公开访问权限 1.1关闭【阻止公共读】 1.2关闭ACL访问控制 1.3打开桶策略 这样桶内所有的图片就能访问了 2.只开放特定文件让其具有访问权限? 2.1关闭【阻止公共读】 如之前的图示 2.2打开ACL控制 2.3单个文件打开公共读...

使用TortoiseGit配合BeyondCompare实现在Git仓库中比对二进制文件
使用TortoiseGit的比对工具可以直接右键,点击选择比对和上一版本的变化差异: 但是TortoiseGit只能支持比对纯文本文件的变化差异,如果尝试比对二进制文件,会提示这不是一个有效的文本文件: BeyondCompare可以比对二进制…...

8、HTTP/1.0和HTTP/1.1的区别【高频】
第一个是 长连接: HTTP/1.0 默认 短连接,(它也可以指定 Connection 首部字段的值为 Keep-Alive实现 长连接)而HTTP/1.1 默认支持 长连接,HTTP/1.1是基于 TCP/IP协议的,创建一个TCP连接是需要经过三次握手的…...

Rk3568驱动开发_开发环境的搭建_1
1、环境说明: 需要用官方的程序包,这个程序需要在虚拟机里编译再将镜像烧录到板子里,本质上是给板子上一套Linux操作系统,镜像是.img文件 Linux操作系统被分成了多个模块,编译好后储存在镜像里,本质上就和…...

Solr中得Core和Collection的作用和关系
Solr中得Core和Collection的作用和关系 一, 总结 在Apache Solr中,Core和Collection 是两个核心概念,他们分别用于单机模式和分布式模式(SolrCloud)中,用于管理和组织数据。 二,Core 定义&am…...

Visual Studio Code 远程开发方法
方法1 共享屏幕远程控制,如 to desk, 向日葵 ,像素太差,放弃 方法2 内网穿透 ssh 第二个方法又很麻烦,尤其是对于 windows 电脑,要使用 ssh 还需要额外安装杂七杂八的东西;并且内网穿透服务提供商提供的…...
如何看到 git 上打 tag 的时间
在 Git 中可以通过以下方法查看标签(tag)的创建时间: 使用 git show 命令: 运行以下命令可以查看某个特定标签的详细信息,包括创建时间: git show 输出中会包含 Tagger 的信息和 Date 字段,显示…...

【HarmonyOS Next】鸿蒙TaskPool和Worker详解 (一)
【HarmonyOS Next】鸿蒙TaskPool和Worker详解 (一) 一、TaskPool和Worker如何实现多线程?各自特点是什么? 在鸿蒙中通过TaskPool和Worker实现多线程并发,两者都基于Actor并发模型实现。 Actor并发模型,每…...
如何设置HTTPOnly和Secure Cookie标志?
设置HttpOnly和Secure标志于Cookie中是增强Web应用安全性的重要措施。这两个标志帮助防止跨站脚本攻击(XSS)和中间人攻击(MitM)。下面是关于如何设置这些标志的具体步骤: 设置方法 在服务器端设置 根据你的服务器端…...

几个api
几个api 原型链 可以阅读此文 Function instanceof Object // true Object instanceof Function // true Object.prototype.isPrototypeOf(Function) // true Function.prototype.isPrototypeOf(Object) // true Object.__proto__ Function.prototype // true Function.pro…...

Deepseek本地部署指南:在linux服务器部署,在mac远程web-ui访问
1. 在Linux服务器上部署DeepSeek模型 要在 Linux 上通过 Ollama 安装和使用模型,您可以按照以下步骤进行操作: 步骤 1:安装 Ollama 安装 Ollama: 使用以下命令安装 Ollama: curl -sSfL https://ollama.com/install.s…...

基于 DeepSeek+AutoGen 的智能体协作系统
用 AutoGen 实现智能体协作流程,假设团队里的 3 个角色,让 3 个角色相互交流后并给出不同方案,最后进行总结。下面是实现的思路,欢迎一起学习交流。 一、系统设计 1. sre_engineer_01 - 问题诊断与初步解决方案 职责:…...

博客系统笔记总结 2( Linux 相关)
Linux 基本使用和程序部署 基本命令 文件操作 显示当前目录下的文件 ls:显示当前目录下的文件 ll:以列表的形式展示,包括隐藏文件 进入目录 && 显示当前路径 cd:进入目录(后面跟相对路径或者绝对路径&…...

计算机毕业设计SpringBoot+Vue.js电影评论网站系统(源码+文档+PPT+讲解)
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...

测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...

Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...

定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...

Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...

spring Security对RBAC及其ABAC的支持使用
RBAC (基于角色的访问控制) RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型,它将权限分配给角色,再将角色分配给用户。 RBAC 核心实现 1. 数据库设计 users roles permissions ------- ------…...