当前位置: 首页 > news >正文

@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知识宝典》 

相关文章:

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

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

LaTeX 2022软件安装教程(附软件下载地址)

软件简介: 软件【下载地址】获取方式见文末。注:推荐使用,更贴合此安装方法! LaTeX 2022是基于ΤΕΧ的一种排版系统,特别适用于生成科技和数学文档的高质量打印。它可用于各种文档类型,从简单信函到完整…...

纯干货分享 机器学习7大方面,30个硬核数据集

在刚刚开始学习算法的时候,大家有没有过这种感觉,最最重要的那必须是算法本身! 其实在一定程度上忽略了数据的重要性。 而事实上一定是,质量高的数据集可能是最重要的! 数据集在机器学习算法项目中具有非常关键的重…...

算法训练营day46

一、单词拆分 元素无重可复选 base case is.length return true,遍历到了最后, 因为ilen s.length,len初始值为1,那么i1 s.length,那么i s.lenth -1 也就是最后一个字符位置 dp(s,i)函数定义:返回 s[i…] 是否能够…...

推荐五个线上兼职,在家也能轻松日入百元,适合上班族和全职宝妈

在这个瞬息万变的时代,你是否也曾考虑过在繁忙的工作之外,寻找一份兼职副业来补贴家用,同时保持生活的多样性?别急,现在就让我为你揭秘五个可靠的日结线上兼职岗位,助你轻松迈向财务自由之路! 一…...

Python_文件操作_学习

目录 一、关于文件的打开和关闭 1. 文件的打开 2.文件的关闭 二、文件的读取 1. 文件的读_r 2. 使用readline 3.使用readlines 三、文件的写入 1. 文本的新建写入 2.文本的追加写入 四、文件的删除和重命名 1.文件的重命名 2.文件的删除 五、文件的定位读写 1.t…...

Leetcode 3154. Find Number of Ways to Reach the K-th Stair

Leetcode 3154. Find Number of Ways to Reach the K-th Stair 1. 解题思路2. 代码实现 题目链接:3154. Find Number of Ways to Reach the K-th Stair 1. 解题思路 这一题思路上就是一个动态规划,我们只需要确定一下运行的终止条件,然后写…...

Vue3/Vite引入EasyPlayer.js播放H265视频错误的问题

一、引入EasyPlayer.js github链接:GitHub - EasyDarwin/EasyPlayer.js: EasyPlayer.js H5播放器 将demo/html目录下的 EasyPlayer-element.min.js、EasyPlayer-lib.min.js、EasyPlayer.wasm、jquery.min.js 复制到vue3工程的public目录下,注意,vue3 vite的index.html文件…...

CentOS 7安装alertmanager

说明:本文介绍如何在CentOS 7安装alertmanager; Step1:下载安装包 访问Github仓库,下载对应版本的alertmanager安装包 https://github.com/prometheus/alertmanager/releases 如何查看自己系统的信息,可参考下图中的…...

YOLOv10详细解读 | 一文带你深入了解yolov10的创新点(附网络结构图 + 举例说明)

前言 Hello大家好,我是Snu77,继YOLOv9发布时间没有多久,YOLOv10就紧接着发布于2024.5.23号(不得不感叹YOLO系列的发展速度,但要纠正大家的观点就是不是最新的就一定最好)! 本文给大家带来的是…...

【openlayers系统学习】3.5colormap详解(颜色映射)

五、colormap详解(颜色映射) ​colormap​ 包是一个很好的实用程序库,用于创建颜色图。该库已作为项目的依赖项添加(1.7美化(设置style))。要导入它,请编辑 main.js​ 以包含以下行…...

Redis教程(十五):Redis的哨兵模式搭建

一、搭建Redis一主二从 分别复制三份Redis工作文件夹,里面内容一致 接着修改7002的配置文件,【redis.windows-service.conf】 port 7002 改成 port 7002 slaveof 127.0.0.1 7001 7003也同样修改 port 7003 slaveof 127.0.0.1 7001 这样就指定了700…...

【C语言】8.C语言操作符详解(3)

文章目录 10.操作符的属性:优先级、结合性10.1 优先级10.2 结合性 11.表达式求值11.1 整型提升11.2 算术转换11.3 问题表达式解析11.3.1 表达式111.3.2 表达式211.3.3 表达式311.3.4 表达式411.3.5 表达式5: 11.4 总结 10.操作符的属性:优先级、结合性 …...

离线初始化k8s

导出和导入所有必要的 Kubernetes 镜像,使用阿里云作为源。 在能访问外网的机器上拉取镜像 首先,在有外网访问的机器上运行以下命令来拉取所有 Kubernetes v1.29.5 版本需要的镜像: kubeadm config images pull --image-repository regist…...

C++字符编码 cppp-reiconv库使用详解

经常写一些控制台小程序,常常会遇到输出中文乱码的问题,在windwos下可以使用MultiByteToWideChar转换字符编码,但跨平台就需要cppp-reiconv这样的第三方字符编码处理库,且开源。 一、下载cppp-reiconv库的源码和静/动态库 GitHu…...

通过继承React.Component创建React组件-5

在React中,V16版本之前有三种方式创建组件(createClass() 被删除了),之后只有两种方式创建组件。这两种方式的组件创建方式效果基本相同,但还是有一些区别,这两种方法在体如下: 本节先了解下用extnds Reac…...

PgSQL内核机制 - 算子执行统计元组个数

PgSQL内核机制 - 算子执行统计元组个数 我们在执行explain analyze观察执行计划执行情况时,时常通过每个算子实际执行结果来分析SQL的执行,其中有一项“rows XXX”表示执行的行数(这里姑且先认为是执行的真实行数)。但有些场景下…...

Ubuntu/Linux 安装Paraview

文章目录 0. 卸载已有ParaView1. 安装ParaView1.1 下载后安装 2.进入opt文件夹改名3. 更改启动项4. 创建硬链接5. 添加桌面启动方式6. 即可使用 0. 卸载已有ParaView YUT 1. 安装ParaView https://www.paraview.org/ 1.1 下载后安装 找到下载的文件夹,文件夹内…...

内存泄漏及其解决方法

1. 系统崩溃前的现象 垃圾回收时间延长:从原本的约10ms增长至50ms,Full GC时间也由0.5s增加至4-5s。Full GC频率增加:最短间隔可缩短至1分钟内发生一次。年老代内存持续增长:即使经过Full GC,年老代内存未见明显释放。…...

Java进阶学习笔记13——抽象类

认识抽象类: 当我们在做子类共性功能抽取的时候,有些方法在父类中并没有具体的体现,这个时候就需要抽象类了。在Java中,一个没有方法体的方法应该定义为抽象方法,而类中如果有抽象方法,该类就定义为抽象类…...

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...

JavaSec-RCE

简介 RCE(Remote Code Execution)&#xff0c;可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景&#xff1a;Groovy代码注入 Groovy是一种基于JVM的动态语言&#xff0c;语法简洁&#xff0c;支持闭包、动态类型和Java互操作性&#xff0c…...

【JavaEE】-- HTTP

1. HTTP是什么&#xff1f; HTTP&#xff08;全称为"超文本传输协议"&#xff09;是一种应用非常广泛的应用层协议&#xff0c;HTTP是基于TCP协议的一种应用层协议。 应用层协议&#xff1a;是计算机网络协议栈中最高层的协议&#xff0c;它定义了运行在不同主机上…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解

【关注我&#xff0c;后续持续新增专题博文&#xff0c;谢谢&#xff01;&#xff01;&#xff01;】 上一篇我们讲了&#xff1a; 这一篇我们开始讲&#xff1a; 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下&#xff1a; 一、场景操作步骤 操作步…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)

宇树机器人多姿态起立控制强化学习框架论文解析 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架&#xff08;一&#xff09; 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)

推荐 github 项目:GeminiImageApp(图片生成方向&#xff0c;可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...

【LeetCode】算法详解#6 ---除自身以外数组的乘积

1.题目介绍 给定一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O…...

绕过 Xcode?使用 Appuploader和主流工具实现 iOS 上架自动化

iOS 应用的发布流程一直是开发链路中最“苹果味”的环节&#xff1a;强依赖 Xcode、必须使用 macOS、各种证书和描述文件配置……对很多跨平台开发者来说&#xff0c;这一套流程并不友好。 特别是当你的项目主要在 Windows 或 Linux 下开发&#xff08;例如 Flutter、React Na…...

Mysql故障排插与环境优化

前置知识点 最上层是一些客户端和连接服务&#xff0c;包含本 sock 通信和大多数jiyukehuduan/服务端工具实现的TCP/IP通信。主要完成一些简介处理、授权认证、及相关的安全方案等。在该层上引入了线程池的概念&#xff0c;为通过安全认证接入的客户端提供线程。同样在该层上可…...

【java】【服务器】线程上下文丢失 是指什么

目录 ■前言 ■正文开始 线程上下文的核心组成部分 为什么会出现上下文丢失&#xff1f; 直观示例说明 为什么上下文如此重要&#xff1f; 解决上下文丢失的关键 总结 ■如果我想在servlet中使用线程&#xff0c;代码应该如何实现 推荐方案&#xff1a;使用 ManagedE…...