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领…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...
k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...
【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...
回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...
计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...
