在现代 Java 应用开发中,异步编程是提升系统吞吐量和响应速度的关键技术之一。Spring 框架提供的@Async注解极大简化了异步方法的实现,让开发者无需手动管理线程即可轻松实现异步操作。本文将从底层原理到实际应用,全面解析@Async注解的工作机制。

一、@Async 注解的核心价值

在同步编程模型中,方法调用是阻塞的 —— 调用方必须等待被调用方法执行完成才能继续执行。这种模式在处理耗时操作(如网络请求、文件 IO、复杂计算)时会严重影响系统响应性。

@Async注解的出现正是为了解决这一问题:它能将被标记的方法转变为异步执行模式,调用方无需等待方法完成即可继续执行后续逻辑,而方法的实际执行会交给独立的线程处理。这种模式特别适合:

  • 非核心业务逻辑(如日志记录、数据统计)
  • 耗时操作(如邮件发送、文件导出)
  • 不需要立即获取结果的场景

二、@Async 的底层实现原理

@Async的实现依赖于 Spring 的两大核心技术:AOP(面向切面编程) 和线程池。其工作流程可分为四个关键步骤:

1. 异步支持的启用:@EnableAsync

使用@Async的前提是在 Spring 配置类上添加@EnableAsync注解。这个注解的核心作用是注册一个关键的后置处理器 ——AsyncAnnotationBeanPostProcessor

AsyncAnnotationBeanPostProcessor的主要职责包括:

  • 扫描容器中所有带有@Async注解的方法
  • 为这些方法所在的 Bean 创建代理对象
  • 协调异步任务的执行机制

源码层面,@EnableAsync通过@Import(AsyncConfigurationSelector.class)导入异步配置选择器,最终注册AsyncAnnotationBeanPostProcessor到 Spring 容器中。

2. 代理对象的创建:AOP 的拦截机制

Spring 容器在初始化带有@Async注解方法的 Bean 时,不会直接创建原始对象,而是通过 AOP 创建一个代理对象。这个代理对象是实现异步调用的关键。

代理对象的创建规则与 Spring AOP 一致:

  • 若目标 Bean 实现了接口,默认使用JDK 动态代理,代理类会实现相同的接口
  • 若目标 Bean 未实现接口,使用CGLIB 代理,通过继承目标类创建代理

代理对象的核心功能是拦截被@Async标记的方法调用。当客户端调用异步方法时,实际上是调用了代理对象的对应方法,而非原始对象的方法。

3. 任务的封装与提交

代理对象拦截方法调用后,并不会立即执行原始方法,而是执行以下操作:

  1. 封装任务:将目标方法、方法参数、目标对象等信息封装成一个CallableRunnable任务对象。对于有返回值的方法,使用Callable;无返回值的方法,使用Runnable

  2. 选择线程池:根据@Async注解的value属性指定的线程池名称,从 Spring 容器中获取对应的TaskExecutor(线程池)。若未指定,使用默认线程池。

  3. 提交任务:将封装好的任务提交到选定的线程池,由线程池中的工作线程负责执行。

  4. 立即返回:代理方法在提交任务后立即返回。对于无返回值的方法,直接返回null;对于有返回值的方法,返回一个Future类型的对象,用于后续获取异步执行结果。

4. 任务的异步执行

线程池中的工作线程从任务队列中获取任务并执行,此时的执行逻辑与调用线程完全分离:

  • 工作线程会调用原始对象的目标方法
  • 方法执行过程中产生的结果会被存储在Future对象中
  • 若方法抛出异常,异常也会被封装在Future中(无返回值方法的异常需要特殊处理)

整个过程中,调用线程与执行线程完全解耦,实现了真正的异步执行。

三、线程池的作用与配置

@Async的异步能力本质上依赖于线程池,线程池在异步执行中扮演着关键角色:

  • 资源管理:控制并发线程数量,避免无限制创建线程导致的系统资源耗尽
  • 性能优化:通过线程复用减少线程创建和销毁的开销
  • 任务排队:提供任务队列缓冲,应对突发的任务峰值

1. 默认线程池的问题

Spring 默认使用SimpleAsyncTaskExecutor作为异步任务执行器,但其存在明显缺陷:

  • 每次执行任务时可能创建新线程(不进行线程复用)
  • 没有最大线程数限制,高并发下可能导致 OOM(内存溢出)
  • 不推荐在生产环境中使用

2. 自定义线程池配置

生产环境中,我们应始终自定义线程池,通过@Bean注解创建ThreadPoolTaskExecutor

java运行

@Configuration
@EnableAsync
public class AsyncConfig {/*** 自定义异步线程池*/@Bean(name = "customAsyncExecutor")public TaskExecutor customAsyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 核心线程数:线程池维护的最小线程数量executor.setCorePoolSize(5);// 最大线程数:线程池允许创建的最大线程数量executor.setMaxPoolSize(10);// 队列容量:用于缓冲等待执行的任务executor.setQueueCapacity(20);// 线程活跃时间:超出核心线程数的线程的最大空闲时间(单位:秒)executor.setKeepAliveSeconds(60);// 线程名称前缀:便于日志跟踪executor.setThreadNamePrefix("Async-Worker-");// 拒绝策略:当任务数量超过最大线程数+队列容量时的处理策略executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());// 初始化线程池executor.initialize();return executor;}
}

3. 指定线程池使用

通过@Async注解的value属性指定使用的线程池:

java运行

@Service
public class AsyncService {// 使用自定义线程池@Async("customAsyncExecutor")public void asyncOperation() {// 异步执行的业务逻辑}
}

四、@Async 的使用场景与代码示例

1. 无返回值的异步方法

适用于不需要获取执行结果的场景,如日志记录、通知发送等:

java运行

@Service
public class NotificationService {@Async("customAsyncExecutor")public void sendEmail(String to, String content) {System.out.println("发送邮件线程:" + Thread.currentThread().getName());// 模拟邮件发送耗时try {Thread.sleep(3000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println("邮件发送至:" + to + ",内容:" + content);}
}

2. 有返回值的异步方法

当需要获取异步执行结果时,方法返回类型需为Future或其实现类(如AsyncResult):

java运行

@Service
public class DataProcessingService {@Async("customAsyncExecutor")public Future<String> processData(String input) {System.out.println("处理数据线程:" + Thread.currentThread().getName());// 模拟数据处理耗时try {Thread.sleep(2000);} catch (InterruptedException e) {Thread.currentThread().interrupt();return new AsyncResult<>("数据处理被中断");}String result = "处理结果:" + input.toUpperCase();return new AsyncResult<>(result);}
}

3. 调用异步方法

java运行

@RestController
@RequestMapping("/async")
public class AsyncController {@Autowiredprivate NotificationService notificationService;@Autowiredprivate DataProcessingService dataProcessingService;@GetMapping("/send-email")public String sendEmail() {System.out.println("控制器线程:" + Thread.currentThread().getName());// 调用无返回值异步方法notificationService.sendEmail("user@example.com", "异步邮件测试");return "邮件发送指令已提交";}@GetMapping("/process-data")public String processData() throws ExecutionException, InterruptedException {System.out.println("控制器线程:" + Thread.currentThread().getName());// 调用有返回值异步方法Future<String> futureResult = dataProcessingService.processData("test input");// 执行其他操作...System.out.println("等待数据处理结果的同时,执行其他任务");// 获取异步执行结果(会阻塞直到结果返回)String result = futureResult.get();return result;}
}

4. 执行结果分析

调用/send-email接口的输出:

控制器线程:http-nio-8080-exec-1
发送邮件线程:Async-Worker-1
邮件发送至:user@example.com,内容:异步邮件测试

调用/process-data接口的输出:

控制器线程:http-nio-8080-exec-2
处理数据线程:Async-Worker-2
等待数据处理结果的同时,执行其他任务
处理结果:TEST INPUT

从输出可以清晰看到:

  • 控制器方法与异步方法在不同线程中执行
  • 控制器线程无需等待异步方法完成即可继续执行

五、@Async 的注意事项与常见问题

1. 方法访问权限必须为 public

@Async注解只对public方法有效。这是因为 Spring AOP 代理机制无法拦截非 public 方法(private、protected、默认访问权限),导致异步失效。

错误示例

java运行

@Service
public class DemoService {// 非public方法,@Async失效@Asyncvoid asyncMethod() {// 业务逻辑}
}

正确示例

java运行

@Service
public class DemoService {// public方法,@Async有效@Asyncpublic void asyncMethod() {// 业务逻辑}
}

2. 避免同类内部方法调用

同一类中的方法 A 调用方法 B(B 被@Async标记)时,调用不会经过代理对象,导致异步失效。这是因为内部调用直接使用this引用,而非代理对象。

错误示例

java运行

@Service
public class OrderService {public void createOrder() {// 内部调用,@Async失效this.sendNotification();}@Asyncpublic void sendNotification() {// 发送通知逻辑}
}

解决方案

java运行

@Service
public class OrderService {@Autowiredprivate OrderService orderService; // 注入自身代理对象public void createOrder() {// 通过代理对象调用,@Async有效orderService.sendNotification();}@Asyncpublic void sendNotification() {// 发送通知逻辑}
}

或使用AopContext获取代理对象:

java运行

@Service
public class OrderService {public void createOrder() {// 获取代理对象OrderService proxy = (OrderService) AopContext.currentProxy();proxy.sendNotification();}@Asyncpublic void sendNotification() {// 发送通知逻辑}
}

注意:使用AopContext需要在启动类添加@EnableAspectJAutoProxy(exposeProxy = true)

3. 异常处理机制

  • 有返回值方法:异常会被封装在Future对象中,调用get()方法时会抛出ExecutionException

  • 无返回值方法:异常默认会被线程池吞没,需要通过AsyncUncaughtExceptionHandler处理:

java运行

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {@Overridepublic AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return (ex, method, params) -> {log.error("异步方法执行异常,方法:{},参数:{}", method.getName(), Arrays.toString(params), ex);};}// 线程池配置...
}

4. 事务管理注意事项

@Async方法与@Transactional注解同时使用时需注意:

  • 异步方法的事务是独立的,与调用方的事务无关
  • 若异步方法需要事务支持,需在异步方法内部添加@Transactional

java运行

@Service
public class OrderService {@Async@Transactional // 异步方法内的事务public void processPayment(Order order) {// 数据库操作(会在独立事务中执行)}
}

六、总结

@Async注解通过 Spring AOP 代理机制和线程池实现了方法的异步执行,其核心原理可概括为:

  1. @EnableAsync开启异步支持,注册关键处理器
  2. Spring 为目标 Bean 创建代理对象
  3. 代理对象拦截@Async方法调用,封装为任务
  4. 任务提交到线程池,由工作线程异步执行
  5. 调用方无需等待,直接返回

掌握@Async的工作原理,不仅能帮助我们正确使用这一特性提升系统性能,还能让我们在遇到问题时快速定位原因。在实际开发中,合理配置线程池、注意方法访问权限和调用方式、完善异常处理机制,才能充分发挥@Async的价值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/99140.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/99140.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/99140.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux C 语言开发 (七) 文件 IO 和标准 IO

文章的目的为了记录使用C语言进行linux 开发学习的经历。开发流程和要点有些记忆模糊&#xff0c;赶紧记录&#xff0c;防止忘记。 相关链接&#xff1a; linux C 语言开发 (一) Window下用gcc编译和gdb调试 linux C 语言开发 (二) VsCode远程开发 linux linux C 语言开发 (…

maven , mvn 运行 项目

提示&#xff1a;环境搭建 文章目录前言一、使用步骤1. 以构建含有 pom.xml 的项目2.mvn 运行具体项目3.mvn 指定模块>运行具体项目前言 提示&#xff1a;版本 spirngboot 3.2 jdk 21 mvn 3.9 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、使…

JVM垃圾回收的时机是什么时候(深入理解 JVM 垃圾回收时机:什么时候会触发 GC?)

深入理解 JVM 垃圾回收时机&#xff1a;什么时候会触发 GC&#xff1f;在 Java 开发中&#xff0c;我们常听说 “JVM 会自动进行垃圾回收”&#xff0c;但很少有人能说清&#xff1a;GC 究竟在什么情况下会被触发&#xff1f;是到固定时间就执行&#xff1f;还是内存满了才会启…

在Vue项目中Axios发起请求时的小知识

在Vue项目中Axios发起请求时的小知识 在Vue项目开发中&#xff0c;Axios作为基于Promise的HTTP客户端&#xff0c;凭借其简洁的API设计和强大的功能&#xff08;如请求/响应拦截、自动JSON转换、取消请求等&#xff09;&#xff0c;已成为前端与后端通信的主流选择。本文将深入…

GeoHash分级索引技术

GeoHash分级索引技术是一种将二维地理坐标转换为一维字符串的空间索引方法,其核心是通过分级网格划分和前缀编码实现高效的空间数据检索。以下从技术原理、实现细节到工程优化展开详细解析: 一、编码原理与分级结构 1. 经纬度二进制化 GeoHash通过递归二分地球表面生成网格…

HTML HTML基础(4)

1.列表 (1).有序列表 概念&#xff1a;有顺序或侧重顺序的列表。 <h2>要把大象放冰箱总共分几步</h2> <ol> <li>把冰箱门打开</li> <li>把大象放进去</li> <li>把冰箱门关上</li> </ol> (2).无序列表 概念&a…

MySQL中的回表操作

在数据库查询&#xff08;尤其是基于 B树索引 的关系型数据库&#xff0c;如MySQL、PostgreSQL&#xff09;中&#xff0c;“回表”是一个核心且高频出现的概念&#xff0c;直接影响查询性能。要理解回表&#xff0c;需先理清索引结构与数据存储的关联&#xff0c;再拆解其发生…

QT子线程与GUI线程安全交互

在Qt应用程序开发中&#xff0c;涉及到多线程处理时&#xff0c;如何安全地从子线程更新UI界面是一个常见的问题。Qt的UI界面并不是线程安全的&#xff0c;意味着你不能直接在子线程中操作UI组件&#xff08;比如按钮、标签等&#xff09;。如果不遵循线程安全的规则&#xff0…

RL【10-2】:Actor - Critic

系列文章目录 Fundamental Tools RL【1】&#xff1a;Basic Concepts RL【2】&#xff1a;Bellman Equation RL【3】&#xff1a;Bellman Optimality Equation Algorithm RL【4】&#xff1a;Value Iteration and Policy Iteration RL【5】&#xff1a;Monte Carlo Learnin…

开源大模型天花板?DeepSeek-V3 6710亿参数MoE架构深度拆解

文章目录认知解构&#xff1a;DeepSeek的定位与核心价值模型概述与发展历程创立初期与技术奠基&#xff08;2023年7月-2024年11月&#xff09;里程碑一&#xff1a;MoE架构规模化突破&#xff08;2024年12月&#xff09;里程碑二&#xff1a;推理成本革命性优化&#xff08;202…

10 训练中的一些问题

&#x1f31f; 大背景&#xff1a;训练神经网络 下山寻宝 训练神经网络就像你蒙着眼在一座大山里&#xff0c;想找最低点&#xff08;最小损失&#xff09;。你只能靠脚下的坡度&#xff08;梯度&#xff09;来决定往哪儿走。 你的位置 模型参数&#xff08;权重 www&#xf…

synchronized锁升级的过程(从无锁到偏向锁,再到轻量级锁,最后到重量级锁的一个过程)

锁升级是 Java 中 synchronized 锁 的核心优化机制&#xff08;基于 JVM 的 对象头 Mark Word 实现&#xff09;&#xff0c;指锁的状态从 无锁 → 偏向锁 → 轻量级锁 → 重量级锁 逐步升级的过程。其目的是通过 “按需升级”&#xff0c;在不同并发场景下选择最优的锁实现&am…

HOT100--Day25--84. 柱状图中最大的矩形,215. 数组中的第K个最大元素,347. 前 K 个高频元素

HOT100–Day25–84. 柱状图中最大的矩形&#xff0c;215. 数组中的第K个最大元素&#xff0c;347. 前 K 个高频元素 每日刷题系列。今天的题目是《力扣HOT100》题单。 题目类型&#xff1a;栈&#xff0c;堆。 84. 柱状图中最大的矩形 思路&#xff1a; class Solution {publ…

基于 Apache Doris 的用户画像数据模型设计方案

一、 需求分析与设计目标数据源&#xff1a;用户基本信息&#xff1a;用户ID、性别、出生日期、注册时间、常驻地域&#xff08;省、市、区&#xff09;、职业等。用户体检报告&#xff1a;每次体检的报告ID、体检时间、各项指标&#xff08;如血压、血糖、血脂、BMI等&#xf…

Python的深度学习

深入理解Python高级特性掌握Python的高级特性是进阶的关键&#xff0c;包括装饰器、生成器、上下文管理器、元类等。这些特性能够提升代码的灵活性和效率。例如&#xff0c;装饰器可以用于实现AOP&#xff08;面向切面编程&#xff09;&#xff0c;生成器可以处理大数据流而无需…

数据库范式(Normalization)

一个设计混乱的数据库就像一个杂乱的房间&#xff0c;用起来非常不方便&#xff1a;东西到处乱放&#xff08;数据冗余&#xff09;&#xff0c;找件东西要翻遍所有角落&#xff08;查询困难&#xff09;&#xff0c;扔掉一把旧椅子时&#xff0c;可能会把搭在上面的唯一一件外…

数据结构---循环队列

基于循环数组实现的循环队列解决了顺序队列中的假溢出导致的空间浪费问题操作&#xff1a;&#xff08;1&#xff09;初始化//循环队列 typedef struct {int *data;//指针模拟声明数组int head,tail;//队头&#xff0c;队尾 }Queue; //初始化 Queue *InitQueue() {Queue *q (Q…

深入理解线程模型

线程作为操作系统调度的基本执行单元&#xff0c;是实现高吞吐、低延迟系统的基础。一、进程与线程的体系结构对比核心概念&#xff1a;进程&#xff08;Process&#xff09;&#xff1a;操作系统资源分配的基本单位&#xff0c;拥有独立的虚拟地址空间、文件描述符表、环境变量…

TTC定时器中断——MPSOC实战3

开启TTC定时器&#xff0c;不同于7000系列的私有定时器此处设置LPD_LSBUS频率TTC频率取决于LPD_LSBUS可前往指定位置查看参数不使能填写对应宏可前往指定位置查看参数main.c#include <stdio.h> #include "xparameters.h" #include "xgpiops.h" #incl…

人工智能训练师三级备考笔记

一、实操1&#xff09;通用语法&#xff08;常见于实操题第一块代码块&#xff09;1.读取文件数据或加载数据集等描述时一般为以下结构&#xff1a;Datapd.read_文件格式(文件名) 注意&#xff1a;文件名需要用‘ ’框起来&#xff0c;必须要有引号文件格式有以下内容csv、txt…