【JUC】一文弄懂@Async的使用与原理

文章目录

  • 1. @Async异步任务概述
  • 2. 深入@Async的底层
    • 2.1 @Async注解
    • 2.2 @EnableAsync注解
    • 2.3 默认线程池

1. @Async异步任务概述

在Spring3.X的版本之后,内置了@Async解决了多个任务同步进行导致接口响应迟缓的情况。

使用@Async注解可以异步执行一个任务,这个任务的返回值必定为null,所以在使用@Async推荐返回值为NULL

那么该如何使用@Async开启一个异步任务呢?

很简单,需要知道两个重要的注解:

  1. @EnableAysnc:启动类上开启异步模式
  2. @Async:需要异步处理的方法
@EnableAsync
@SpringBootApplication
public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);log.info("Project running .....");}}
@Service
public class UserServiceImpl implements UserService {@Asyncpublic void test()  {log.info("test");}}

需要注意的是,@Async的异步任务是基于AOP实现的,如果是自调用的情况下,@Async是不会生效的喔。


2. 深入@Async的底层

@Async注解可以将一个任务异步调用,那么就说明其底层帮我们创建了一个线程池,使用线程池的线程调用这个异步方法,那我们就开始扒一扒这个注解的底层是什么东东吧。


2.1 @Async注解

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async {/*** A qualifier value for the specified asynchronous operation(s).* <p>May be used to determine the target executor to be used when executing* the asynchronous operation(s), matching the qualifier value (or the bean* name) of a specific {@link java.util.concurrent.Executor Executor} or* {@link org.springframework.core.task.TaskExecutor TaskExecutor}* bean definition.* <p>When specified on a class-level {@code @Async} annotation, indicates that the* given executor should be used for all methods within the class. Method-level use* of {@code Async#value} always overrides any value set at the class level.* @since 3.1.2*/String value() default "";}

指定异步操作的限定符值。 可用于确定执行异步操作时要使用的目标执行器,与特定executor或TaskExecutor bean定义的限定符值(或bean名称)匹配。 当在类级别@Async注释上指定时,指示给定的执行器应用于类中的所有方法。方法级别对Async#值的使用始终覆盖在类级别设置的任何值。

自: 3.1.2

从注解的注释来看,可以看出这个@Async里只有一个value属性,这个属性其实就是Bean的名称,如果不为null,则从Spring容器中获取这个Bean。

并且@Async的返回值只能是 voidFuture

当我们需要返回值的时候,可以使用 对象对返回结果包一层,否则就会返回NULL

image-20230925212453775

image-20230925212510262

但是当我们使用AsyncResult包一层,就会获取返回结果的对象。

image-20230925212729176

image-20230925212802258


2.2 @EnableAsync注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {/*** Indicate the 'async' annotation type to be detected at either class* or method level.* <p>By default, both Spring's @{@link Async} annotation and the EJB 3.1* {@code @javax.ejb.Asynchronous} annotation will be detected.* <p>This attribute exists so that developers can provide their own* custom annotation type to indicate that a method (or all methods of* a given class) should be invoked asynchronously.*/Class<? extends Annotation> annotation() default Annotation.class;/*** Indicate whether subclass-based (CGLIB) proxies are to be created as opposed* to standard Java interface-based proxies.* <p><strong>Applicable only if the {@link #mode} is set to {@link AdviceMode#PROXY}</strong>.* <p>The default is {@code false}.* <p>Note that setting this attribute to {@code true} will affect <em>all</em>* Spring-managed beans requiring proxying, not just those marked with {@code @Async}.* For example, other beans marked with Spring's {@code @Transactional} annotation* will be upgraded to subclass proxying at the same time. This approach has no* negative impact in practice unless one is explicitly expecting one type of proxy* vs. another &mdash; for example, in tests.*/boolean proxyTargetClass() default false;/*** Indicate how async advice should be applied.* <p><b>The default is {@link AdviceMode#PROXY}.</b>* Please note that proxy mode allows for interception of calls through the proxy* only. Local calls within the same class cannot get intercepted that way; an* {@link Async} annotation on such a method within a local call will be ignored* since Spring's interceptor does not even kick in for such a runtime scenario.* For a more advanced mode of interception, consider switching this to* {@link AdviceMode#ASPECTJ}.*/AdviceMode mode() default AdviceMode.PROXY;/*** Indicate the order in which the {@link AsyncAnnotationBeanPostProcessor}* should be applied.* <p>The default is {@link Ordered#LOWEST_PRECEDENCE} in order to run* after all other post-processors, so that it can add an advisor to* existing proxies rather than double-proxy.*/int order() default Ordered.LOWEST_PRECEDENCE;}

@EnableAsync注解中,最重要的一行代码就是@Import(AsyncConfigurationSelector.class),其他的不作解释,感兴趣的可以自行阅读源码的注释理解学习。

@Import(AsyncConfigurationSelector.class)这行代码中,引入了相关的配置类,让我们看可以看AsyncConfigurationSelector这个类有什么东西

public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME ="org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";/*** Returns {@link ProxyAsyncConfiguration} or {@code AspectJAsyncConfiguration}* for {@code PROXY} and {@code ASPECTJ} values of {@link EnableAsync#mode()},* respectively.*/@Override@Nullablepublic String[] selectImports(AdviceMode adviceMode) {switch (adviceMode) {case PROXY:return new String[] {ProxyAsyncConfiguration.class.getName()};case ASPECTJ:return new String[] {ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME};default:return null;}}}

里面只有一个方法selectImports,这个方法就是一个选择器,根据不同的代理模式,加载不同的配置类。

首先我们看看ProxyAsyncConfiguration这个配置类。

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public AsyncAnnotationBeanPostProcessor asyncAdvisor() {Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected");//初始化AsyncAnnotationBeanPostProcessor类型的beanAsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();//设置执行器和异常处理器bpp.configure(this.executor, this.exceptionHandler);Class<? extends Annotation> customAsyncAnnotation = this.enableAsync.getClass("annotation");//设置annotationif (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {bpp.setAsyncAnnotationType(customAsyncAnnotation);}//设置注解属性bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));bpp.setOrder(this.enableAsync.<Integer>getNumber("order"));return bpp;}}

ProxyAsyncConfiguration配置类继承了AbstractAsyncConfiguration类,这个类用于配置异步任务的线程池与执行器。它提供了一些默认实现,可以使配置变得更加简单和快捷,并且易于扩展和定制。

在这ProxyAsyncConfiguration配置类中,初始化了一个AsyncAnnotationBeanPostProcessorAsyncAnnotationBeanPostProcessor是Spring框架中的一个后置处理器,用于处理带有@Async注解的方法。它的主要作用是将这些方法转换为异步执行的任务,并使用适当的线程池进行调度和管理。

asyncAdvisor方法定义了一个Bean后置处理器,负责解析带@Async注解的方法,并将其包装成一个异步任务

那么异步任务在哪包装的呢?

我们把目光投到org.springframework.aop.interceptor.AsyncExecutionAspectSupport类中,里面有一个invoke方法,这个方法负责拦截给定方法的调用,将方法的实际调用提供给正确的任务执行器,并返回个调用者

@Override
@Nullable
public Object invoke(final MethodInvocation invocation) throws Throwable {Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);// 获取线程池AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);if (executor == null) {throw new IllegalStateException("No executor specified and no default executor set on AsyncExecutionInterceptor either");}// 将方法调用封装成一个任务Callable<Object> task = () -> {try {Object result = invocation.proceed();if (result instanceof Future) {return ((Future<?>) result).get();}}catch (ExecutionException ex) {handleError(ex.getCause(), userDeclaredMethod, invocation.getArguments());}catch (Throwable ex) {handleError(ex, userDeclaredMethod, invocation.getArguments());}return null;};// 提交任务return doSubmit(task, executor, invocation.getMethod().getReturnType());
}

determineAsyncExecutor方法分为三个部分

  1. 获取@Async注解的value值,这个值其实就是Bean的名称,如果不为空则从Spring容器获取对应的Bean
  2. 如果value为null,则找到默认线程池
  3. 最后,不论是默认的线程池还是 Spring 容器中我们自定义的线程池,都会以方法为维度,在 map 中维护方法和线程池的映射关系

image-20230930113321687

image-20230930112446442


2.3 默认线程池

从前面得知,成员变量defaultExecutor是默认的线程池,那么这个默认线程池是怎么样的呢?

这时候我们需要关注org.springframework.aop.interceptor.AsyncExecutionAspectSupport#getDefaultExecutor方法。

1、在没有配置线程池的情况下,使用ThreadPoolTaskExecutor

2、配置了线程池,但是没有用taskExecutor,会找不到,就用SimpleAsyncTaskExecutor

3、配置线程池,并且指定beanName为taskExecutor,就会直接使用这个配置的线程池

@Nullable
protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {if (beanFactory != null) {try {// Search for TaskExecutor bean... not plain Executor since that would// match with ScheduledExecutorService as well, which is unusable for// our purposes here. TaskExecutor is more clearly designed for it.return beanFactory.getBean(TaskExecutor.class);}catch (NoUniqueBeanDefinitionException ex) {logger.debug("Could not find unique TaskExecutor bean. " +"Continuing search for an Executor bean named 'taskExecutor'", ex);try {return beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class);}catch (NoSuchBeanDefinitionException ex2) {if (logger.isInfoEnabled()) {logger.info("More than one TaskExecutor bean found within the context, and none is named " +"'taskExecutor'. Mark one of them as primary or name it 'taskExecutor' (possibly " +"as an alias) in order to use it for async processing: " + ex.getBeanNamesFound());}}}catch (NoSuchBeanDefinitionException ex) {logger.debug("Could not find default TaskExecutor bean. " +"Continuing search for an Executor bean named 'taskExecutor'", ex);try {return beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class);}catch (NoSuchBeanDefinitionException ex2) {logger.info("No task executor bean found for async processing: " +"no bean of type TaskExecutor and no bean named 'taskExecutor' either");}// Giving up -> either using local default executor or none at all...}}return null;
}

通过DEBUG可以看到默认使用的ThreadPoolTaskScheduler

image-20230930115528112


参考:

  1. @Async注解其实也就这么回事。 - 掘金 (juejin.cn)
  2. Spring异步编程 | 你的@Async就真的异步吗 ☞ 异步历险奇遇记 - 掘金 (juejin.cn)
  3. @Async异步任务与线程池 - 掘金 (juejin.cn)
  4. @Async注解使用与原理分析 - 知乎 (zhihu.com)

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Docker清理

title: “Mysql安装” createTime: 2022-01-04T20:07:3108:00 updateTime: 2022-01-04T20:07:3108:00 draft: false author: “name” tags: [“mysql”] categories: [“docker”] description: “测试的” docker-mysql安装部署文档 文章目录 title: "Mysql安装" …

玩转gpgpu-sim 04记—— __cudaRegisterBinary() of gpgpu-sim 到底做了什么

官方文档&#xff1a; GPGPU-Sim 3.x Manual __cudaRegisterBinary(void*) 被执行到的代码逻辑如下&#xff1a; void** CUDARTAPI __cudaRegisterFatBinary( void *fatCubin ) { #if (CUDART_VERSION < 2010)printf("GPGPU-Sim PTX: ERROR ** this version of GPGPU…

flink中不同序列化器性能对比

背景 flink有多种序列化方式&#xff0c;包括flink内置的以及fallback到kryo的&#xff0c;那么他们之间有多大的性能差距呢&#xff0c;本文就从https://flink.apache.org/2020/04/15/flink-serialization-tuning-vol.-1-choosing-your-serializer-if-you-can/这篇文章里摘录…

【微服务保护】

文章目录 Sentinel 微服务雪崩问题&#xff1a; 微服务中&#xff0c;服务间调用关系错综复杂&#xff0c;一个微服务往往依赖于多个其它微服务。服务D有 故障进而导致服务A有故障&#xff0c;进而导致服务雪崩。 解决雪崩问题的常见方式有四种&#xff1a; 超时处理&#xff1…

c# 中的类

反射 Activator.CreateInstance class Program {static void Main(string[] args){//反射Type t typeof(Student);object o Activator.CreateInstance(t, 1, "FJ");Student stu o as Student;Console.WriteLine(stu.Name);//动态编程dynamic stu2 Activator.Cre…

java生成PDF的Util

java使用itext生成pdf-CSDN博客 接上文 支持绘制表格 支持表格中的文本 字体加粗、字体上色、单元格背景上色&#xff0c; 支持拼接文本 支持单行文本 多种背景颜色、字体上色 支持自定义水印 废话不说先上效果图 工具类代码 package com.zxw.文件.PDF.util;import com.…

Stm32_标准库_5_呼吸灯_按键控制

Stm32按键和输出差不多 PA1为LED供给正电&#xff0c;PB5放置按键&#xff0c;按键一端接PB5,另一端接负极 void Key_Init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //APB2总线连接着GPIOBGPIO_InitStructur.GPIO_Mode GPIO_Mode_IPU;GPIO_InitStructur.…

Kafka:介绍和内部工作原理

展示Kafka工作方式的简单架构。 什么是Kafka&#xff1f;为什么我们要使用它&#xff1f;它是消息队列吗&#xff1f; •它是一个 分布式流处理平台或分布式 提交日志*。*•Kafka通常用于实时流数据管道&#xff0c;即在系统之间传输数据&#xff0c;构建不断流动的数据转换系统…

什么是GraphQL?它与传统的REST API有什么不同?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 什么是GraphQL&#xff1f;⭐ 与传统的REST API 的不同⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣…

Konva基本处理流程和相关架构设计

前言 canvas是使用JavaScript基于上下文对象进行2D图形的绘制的HTML元素&#xff0c;通常用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面。基于Canvas之上&#xff0c;诞生了例如 PIXI、ZRender、Fabric、Konva等 Canvas渲染引擎&#xff0c;兼顾易用的同时…

DataBinding双向绑定简介

一、简介 在Vue中使用的是MVVM架构。通过ViewModel可以实现M层和V层数据的双向绑定。Model层的数据发生变化后&#xff0c;会自动更新View层UI。UI层数据发生变化&#xff08;用户输入&#xff09;&#xff0c;可以驱动Model层的数据发生变化&#xff0c;借助于Vue框架中的View…

Spring cloud Sentinel介绍和安装

Sentinel介绍和安装 &#x1f308;初识Sentinel&#x1f308;安装Sentinel&#x1f320;docker 安装&#x1f320;下载sentinel镜像&#x1f320;启动sentinel镜像 &#x1f320;windows 安装&#x1f320;下载&#x1f320;运行 &#x1f320;sentinel访问 &#x1f308;微服务…

React 全栈体系(十四)

第七章 redux 六、react-redux 7. 代码 - react-redux 数据共享版 7.1 效果 7.2 App /* src/App.jsx */ import React, { Component } from "react"; import Count from "./containers/Count"; import Person from "./containers/Person";ex…

26947-2011 手动托盘搬运车 学习记录

声明 本文是学习GB-T 26947-2011 手动托盘搬运车. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了手动托盘搬运车(以下简称托盘车)的结构参数、技术要求、试验方法、检验规则、 标志、包装、运输和贮存。 本标准适用于额定载荷…

【数据结构--八大排序】之冒泡排序+选择排序+插入排序

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

2023彩虹全新SUP模板,知识付费模板,卡卡云模板

源码介绍&#xff1a; 2023彩虹全新SUP模板/知识付费模板/卡卡云模板&#xff0c;首页美化&#xff0c;登陆页美化&#xff0c;修复了pc端购物车页面显示不正常的问题。 请自行查毒。感觉彩虹不少源码可能都有不干净的东西 安装教程&#xff1a; 1.将这俩个数据库文件导入数据…

2023.09.30使用golang1.18编译Hel10-Web/Databasetools的windows版

#Go 1.21新增的 log/slog 完美解决了以上问题&#xff0c;并且带来了很多其他很实用的特性。 本次编译不使用log/slog 包 su - echo $GOPATH ;echo $GOROOT; cd /tmp; busybox wget --no-check-certificate https://go.dev/dl/go1.18.linux-amd64.tar.gz;\ which tar&&am…

Java on Azure Tooling 8月更新|以应用程序为中心的视图支持及 Azure 应用服务部署状态改进

作者&#xff1a;Jialuo Gan - Program Manager, Developer Division at Microsoft 排版&#xff1a;Alan Wang 大家好&#xff0c;欢迎阅读 Java on Azure 工具的八月更新。在本次更新中&#xff0c;我们将推出新的以应用程序为中心的视图支持&#xff0c;帮助开发人员在一个项…

Python计算巴氏距离

Python计算巴氏距离 巴氏距离简介 在统计中&#xff0c;巴氏距离&#xff08;Bhattacharyya Distance&#xff09;测量两个离散或连续概率分布的相似性。它与衡量两个统计样品或种群之间的重叠量的巴氏系数密切相关。巴氏距离和巴氏系数以20世纪30年代曾在印度统计研究所工作…

<Xcode> Xcode IOS无开发者账号打包和分发

关于flutter我们前边聊到的初入门、数据解析、适配、安卓打包、ios端的开发和黑苹果环境部署&#xff0c;但是对于苹果的打包和分发&#xff0c;我只是给大家了一个链接&#xff0c;作为一个顶级好男人&#xff0c;我认为这样是对大家的不负责任&#xff0c;那么这篇就主要是针…