@Async详解,为什么生产环境不推荐直接使用@Async?

一、@Async 注解介绍:

@Async 注解用于声明一个方法是异步的。当在方法上加上这个注解时,Spring 将会在一个新的线程中执行该方法,而不会阻塞原始线程。这对于需要进行一些异步操作的场景非常有用,比如在后台执行一些耗时的任务而不影响前台响应。

示例:

@Service
public class MyService {@Asyncpublic void asyncMethod() {// 异步执行的代码}
}

在上面的例子中,asyncMethod 方法使用 @Async 注解标记,表示该方法将在一个独立的线程中执行。

二、@Async 底层分析

1、整体思维导图

2、注解底层实现分析

@Async的实现大概分为以下几个步骤:

  1. // 1、目标代理类

  2. ProxyFactory proxyFactory = new ProxyFactory();

  3. proxyFactory.setTarget("DemoImpl bean");

  4. // 2、代理接口

  5. proxyFactory.addInterface("DemoService");

  6. // 3、设置切点

  7. AsyncAnnotationAdvisor.pointcut = @Async注解

  8. // 4、环绕通知处理

  9. AsyncAnnotationAdvisor.advice = AnnotationAsyncExecutionInterceptor拦截器

  10. // 5、切面 = 切点+通知

  11. proxyFactory.addAdvisor("AsyncAnnotationAdvisor");

  12. // 6、生成代理

  13. UserService userService = proxyFactory.getProxy(getProxyClassLoader());

其中设置切点是在AsyncAnnotationAdvisor类里面

环绕通知处理是在AnnotationAsyncExecutionInterceptor这个拦截器去做的我们主要就分析一下这个拦截器相关的代码,可以看到直接调用了super(defaultExecutor, exceptionHandler),是在它的父类实现的。

我们看看它的父类

这个类有四个方法:invoke()、getExecutorQualifier()、getDefaultExecutor()、getOrder(),我们主要关注Invoke()方法和getDefaultExecutor()方法。

Invoke()方法如下:

        它的作用就是会找出标注了@Async注解的方法,然后生成一个Callable对象,并提交给线程池的一个线程来执行,从而实现了该方法的异步执行。

  1. 通过AOPUtils.getTargetClass(invocation.getThis()) 获取目标对象的类
  2. 使用 ClassUtils.getMostSpecificMethod() 方法获取指定目标类和方法
  3. 通过 BridgeMethodResolver.findBridgedMethod() 方法处理桥接方法,确保获取到用户声明的方法。(桥接方法用来解决运行时泛型被擦除问题)
  4. determineAsyncExecutor() 方法,根据用户声明的方法确定异步执行器。
  5. 定义一个 Callable 类型的任务,使用 Lambda 表达式创建了一个匿名函数作为任务内容,通过 invocation.proceed() 执行目标方法,并处理可能的异常情况。

  6. this.doSubmit(task, executor, invocation.getMethod().getReturnType()):将任务提交给异步执行器进行处理,并返回执行结果。

getDefaultExecutor()方法如下:

       首先尝试获取 TaskExecutor 实现类的 bean 对象,如果能找到且只有一个,则返回该对象;如果找不到或者找到了多个,则会进入 catch 语句块分支,获取 beanName 为 taskExecutor 的 bean对象,如果获取不到,则会创建一个 SimpleAsyncTaskExecutor 线程池对象。

换句话说,如果直接使用 @Async 注解,Spring 就会直接使用 SimpleAsyncTaskExecutor 线程池。那直接使用 SimpleAsyncTaskExecutor 线程池会出现什么问题呢?现在我们进入第三个主题。

三、@Async 存在的问题或注意事项

1、线程池问题

上面说到,如果直接使用 @Async 注解,Spring 就会直接使用 SimpleAsyncTaskExecutor 线程池。

1)让我们看看这个线程池到底是怎么执行的。

可以看到,它是直接 new 一个线程然后直接开启线程。

他既没有重用线程,也没有设置最大线程数,所以在并发量大的时候会产生严重的性能问题!所以一般在生产环境,特别是 toc 的项目,不建议直接使用 @Async 注解,应该使用自定义线程池搭配 @Async 注解一起使用!

2)那如何配置@Async的自定义线程池呢?

在Spring中,我们可以通过实现 AsyncConfigurer 接口或者直接继承 AsyncConfigurerSupport 类来自定义 Async 异步线程池;

线程池可以直接使用 JDK 提供的 ThreadPoolExecutor 或者 Spring 本身也提供的 TaskExecutor 的实现类,Spring的实现类有以下五种,其中使用最多的就是 ThreadPoolTaskExecutor。

  1. SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。
  2. SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地
  3. ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类
  4. ThreadPoolTaskScheduler:可以使用cron表达式
  5. ThreadPoolTaskExecutor:最常使用,推荐。其实质是对java.util.concurrent.ThreadPoolExecutor的包装

接下来,我们就以 ThreadPoolTaskExecutor 为例来自定义一个线程池:

2、与 @Transactional 联用问题

        当然,使用@Async还有一些别的注意事项,比如与@Transcational联用时,它们在同一个方法上同时使用时可能导致异步失效。这是因为 @Async 通常会使用一个新的线程,而新线程无法继承原始线程的事务上下文。

        解决办法是将 @Async 注解放在另外的类或者方法上,确保异步方法被另外的代理类包装。这样,异步方法就能够在独立的线程中执行,同时也能够继承事务上下文。

@Service
public class MyService {@Asyncpublic void asyncMethodWithTransaction() {transactionalMethod();}@Transactionalpublic void transactionalMethod() {// 事务性操作的代码}
}

3、循环依赖问题

现在有两个类,ServiceA 和 ServiceB 如下:

@Service
public class ServiceA {private final ServiceB serviceB;@Autowiredpublic ServiceA(ServiceB serviceB) {this.serviceB = serviceB;}@Asyncpublic void asyncMethod() {// 异步方法逻辑}
}
@Service
public class ServiceB {private final ServiceA serviceA;@Autowiredpublic ServiceB(ServiceA serviceA) {this.serviceA = serviceA;}
}

其中serviceA、serviceB对象之间相互依赖,serviceA和serviceB总有一个会先实例化,而serviceA或serviceB里面使用了@Async注解,会导致循环依赖异常:

org.springframework.beans.factory.BeanCurrentlyInCreationException

在springboot中,以上报错被捕捉,抛出的异常是:

The dependencies of some of the beans in the application context form a cycle

原因

        我们知道,spring三级缓存一定程度上解决了循环依赖问题。A对象在实例化之后,属性赋值【opulateBean(beanName, mbd, instanceWrapper)】执行之前,将ObjectFactory添加至三级缓存中,从而使得在B对象实例化后的属性赋值过程中,能从三级缓存拿到ObjectFactory,调用getObject()方法拿到A的引用,B由此能顺利完成初始化并加入到IOC容器。此时A对象完成属性赋值之后,将会执行初始化【initializeBean(beanName, exposedObject, mbd)方法】重点是@Async注解的处理正是在这地方完成的,其对应的后置处理器AsyncAnnotationBeanPostProcessor,在postProcessAfterInitialization方法中将返回代理对象,此代理对象与B中持有的A对象引用不同,导致了以上报错。

解决办法

  1. 在A类上加@Lazy,保证A对象实例化晚于B对象
  2. 不使用@Async注解,通过自定义异步工具类发起异步线程(线程池)
  3. 不要让@Async的Bean参与循环依赖

@Async循环依赖问题参考博客https://cloud.tencent.com/developer/article/1497689

ps:以下是我整理的java面试资料,感兴趣的可以看看。最后,创作不易,觉得写得不错的可以点点关注!

链接:https://www.yuque.com/u39298356/uu4hxh?# 《Java知识宝典》 

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

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

相关文章

JUC框架(Semaphore、CountDownLatch、CyclicBarrier)

文章目录 Semaphore(信号量)Semaphore介绍Semaphore基本概念Semaphore使用场景Semaphore示例 CountDownLatch (计数器/闭锁)CountDownLatch 介绍CountDownLatch 基本概念CountDownLatch 使用场景CountDownLatch 基本方法CountDownLatch 示例 CyclicBarri…

分布式微服务之springboot学习

跟着韩顺平老师学Java SpringBoot基本介绍官方文档springboot是什么?springboot快速入门需求/图解说明完成步骤快速入门小结 Spring SpringMVC SpringBoot的关系梳理关系如何理解 -约定优于配置 依赖管理和自动配置依赖管理什么是依赖管理修改自动仲裁/默认版本号 starter场景…

mac 安装java jjdk8 jdk11 jdk17 等

oracle官网 https://www.oracle.com/java/technologies/downloads/ 查看当前电脑是英特尔的x86 还是arm uname -m 选择指定版本,指定平台的安装包: JDK8 JDK11的,需要当前页面往下拉: 下载到的安装包,双击安装&#x…

ChatGLM3-6B部署

ZhipuAI/chatglm3-6b 模型文件地址 ChatGLM3 代码仓库 ChatGLM3 技术文档 硬件环境 最低要求: 为了能够流畅运行 Int4 版本的 ChatGLM3-6B,最低的配置要求: 内存:> 8GB 显存: > 5GB(1060 6GB,2060 6G…

[力扣]——231.2的幂

题目描述: 给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false 。 如果存在一个整数 x 使得 n 2x ,则认为 n 是 2 的幂次方。 bool isPowerOfTwo(int n){ if(n0)retur…

3D技术的应用领域

3D技术在现代科技和工业中有广泛的应用,其涵盖的领域非常广泛,从娱乐到医学,再到制造业和建筑,3D技术正在改变我们理解和互动的方式。以下是一些主要的应用领域。北京木奇移动技术有限公司,专业的软件外包开发公司&…

如何获取某个城市或区域的人口分布数据?

人口分布数据在多个领域都扮演着至关重要的角色。这些数据不仅反映了一个国家或地区的人口分布状况,而且为政策制定者、企业决策者和研究者提供了宝贵的信息。那么,我们如何获取这些重要的人口分布数据呢? 政府统计部门是最主要的来源。各国政…

通用代码生成器应用场景二,快速原型

通用代码生成器应用场景二,快速原型 对项目经理,产品经理和售前工程师而言,开发快速原型是一种常见的需求。使用通用代码生成器,您可以更好,更快的开发系统的快速原型。通用代码生成器对完成CRUD和登录系统阶段的快速…

2024 NahamConCTF re 部分wp

IPromise 附件拖入ida main里没东西,但是函数列表很明显。直接在线网站解 Taylors First Swift 附件拖入ida,会提示识别到结构,选择yes 比较简单,可以直接猜异或之后再base64 Whats in the Box? 新遇见的题型,是用mak…

四元数学习总结(2)

导语:相比矩阵,用四元数处理3D旋转的优势是毋庸置疑的,但由于概念复杂,难于理解,一直令我摸不着头脑。最近学习更是发现在机器人、无人机、SLAM等先进领域,四元数被当成实数、整数这样的基础,所…

抄单 高频下单系统的功能都有什么?

1、多开:同一台电脑同一个账户无限数量登录(登录后可独立新开合约,挂单,买卖等),多开后可使用三种不同切换方式来回切换,第一种为ALTTab切换、第二种为点击搜索条切换、第三种为点击下方任务栏切…

RT-Thread更改msh串口波特率

修改rt-thread文件下components下dirvers下serial.h文件里 #define RT_SERIAL_CONFIG_DEFAULT 里的默认波特率即可

Vue进阶之Vue项目实战(三)

Vue项目实战 图表渲染安装echarts图表渲染器(图表组件)图表举例:创建 ChartsRenderer.vue创建 ChartsDataTransformer.ts 基于 zrender 开发可视化物料安装 zrender画一个矩形画一个柱状图 基于svg开发可视化物料svg小示例使用d3进行图表渲染安装d3基本使用地图绘制…

柏拉图表征假说:AI模型趋同于现实的统一表征

引言 近日,Ilya Sutskever在离开OpenAI后不久点赞了一篇由MIT团队发表的AI论文,这篇题为《The Platonic Representation Hypothesis》的论文引起了广泛关注。这篇论文探讨了AI模型在不同数据和模态上的训练是否趋向于收敛成一个共享的现实世界统计模型。…

怎么识别图片中的文字呢!??

要识别图片中的文字,一般使用OCR软件来实现这一需求,下面以金某识别网页版为例,说说操作步骤: 一、点击“点击添加需转换的图片或PDF”,如还没登录将弹出登录窗口,直接登录即可,如已登录&#x…

基于 Wireshark 分析 UDP 协议

一、UDP 协议 UDP(User Datagram Protocol,用户数据报协议)是一种无连接的传输层协议,常用于传输即时数据,如音频、视频和实时游戏数据等。 UDP 的特点如下: 1. 无连接性:UDP 不需要在发送数…

计算机毕业设计 | SSM汽车租赁系统(附源码)

1, 概述 1.1 课题背景 随着社会的快速发展,计算机的影响是全面且深入的。用户生活水平的不断提高,日常生活中用户对汽车租赁系统方面的要求也在不断提高,需要汽车租赁系统查询的人数更是不断增加,使得汽车租赁系统的…

6-3 求二叉树的深度

作者 DS课程组 单位 临沂大学 本题要求实现一个函数,可返回二叉树的深度。 函数接口定义: int Depth(BiTree T);T是二叉树树根指针,函数Depth返回二叉树的深度,若树为空,返回0。 裁判测试程序样例: #in…

电商API接口:供应商价格与主流电商平台价格做比价

品牌在进行采购工作时,将供应商提供的价格与主流电商平台上的公开价格进行比价是一种非常常见的做法,这样做的目的主要是为了保证自身供应商提供的价格具有竞争力和合理性,从而更好地优化采购工作。 以下是过程中的具体步骤及一些注意事项&a…

基于springboot实现周边游平台个人管理系统项目【项目源码+论文说明】

基于springboot实现周边游平台个人管理系统演示 摘要 在如今社会上,关于信息上面的处理,没有任何一个企业或者个人会忽视,如何让信息急速传递,并且归档储存查询,采用之前的纸张记录模式已经不符合当前使用要求了。所以…