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

news2024/11/29 18:41:17

文章目录

  • 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 {

    @Async
    public 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
   @Nullable
   public 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类型的bean
      AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
      //设置执行器和异常处理器
      bpp.configure(this.executor, this.exceptionHandler);
      Class<? extends Annotation> customAsyncAnnotation = this.enableAsync.getClass("annotation");
      //设置annotation
      if (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.coloradmin.cn/o/1052080.html

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

相关文章

棱镜七彩受邀参加“数字政府建设暨数字安全技术研讨会”

近日&#xff0c;为深入学习贯彻党的二十大精神&#xff0c;落实《数字中国建设整体布局规划》中关于“发展高效协同的数字政务”的要求&#xff0c;由国家信息中心主办、复旦大学义乌研究院承办、苏州棱镜七彩信息科技有限公司等单位协办的“数字政府建设暨数字安全技术研讨会…

zemax埃尔弗目镜

可以认为是一种对称设计&#xff0c;在两个双胶合透镜之间增加一个双凹单透镜 将半视场增大到30&#xff0c;所有的轴外像差维持在可以接受的水平。 入瞳直径4mm波长0.51、0.56、0.61半视场30焦距27.9mm 镜头参数&#xff1a; 成像效果&#xff1a;

Win11配置多个CUDA环境

概述 由于跑项目发现需要配置不同版本的Pytorch&#xff0c;而不同版本的Pytorch又对应不同版本的CUDA&#xff0c;于是有了在Win上装多个CUDA的打算 默认已经在电脑上装了一个CUDA 现在开始下载第二个CUDA版本&#xff0c;前面下载的操作和普通安装的几乎一样 安装CUDA CU…

CFS内网穿透靶场实战

一、简介 不久前做过的靶场。 通过复现CFS三层穿透靶场&#xff0c;让我对漏洞的利用&#xff0c;各种工具的使用以及横向穿透技术有了更深的理解。 一开始nmap探测ip端口,直接用thinkphpv5版本漏洞工具反弹shell&#xff0c;接着利用蚁剑对服务器直接进行控制&#xff0c;留下…

识别消费陷阱,反消费主义书单推荐

在消费主义无所不在的今天&#xff0c;商家是如何设置消费陷阱的&#xff1f;人们在做出消费决策时又是如何“犯错”的&#xff1f;如何才能做出更加理性的选择&#xff1f; 本书单适合对经济学、市场营销感兴趣的朋友阅读。 《小狗钱钱》 “你的自信程度决定了你是否相信自已…

kaggle_competition1_CIFAR10_Reg

一、查漏补缺、熟能生巧&#xff1a; 1.关于shutil.copy或者这个copyfile的作用和用法&#xff1a; 将对应的文件复制到对应的文件目录下 2.关于python中dict的键值对的获取方式&#xff1a; #终于明白了&#xff0c;原来python中的键_值 对的用法就是通过调用dict.keys()和…

Windows/Linux下进程信息获取

Windows/Linux下进程信息获取 前言一、windows部分二、Linux部分三、完整代码四、结果 前言 Windows/Linux下进程信息获取&#xff0c;目前可获取进程名称、进程ID、进程状态 理论分析&#xff1a; Windows版本获取进程列表的API: CreateToolhelp32Snapshot() 创建进程快照,…

GPIO的输入模式

1. GPIO支持4种输入模式&#xff08;浮空输入、上拉输入、下拉输入、模拟输入&#xff09; 1. 模拟输入 首先GPIO输出部分(N-MOS,P-MOS)是不起作用的。并且TTL施密特触发器也是不工作的。 上下拉电阻的开关都是关闭的。相当于I/o直接接在模拟输入。 模拟输入模式下&#xff…

测试开源下载模块Downloader

微信公众号“DotNet”的文章《.NET 异步、跨平台、支持分段下载的开源项目 》&#xff08;参考文献1&#xff09;介绍了GitHub中的开源下载模块Downloader的基本用法&#xff0c;本文学习Downloader的主要参数设置方式及基本用法&#xff0c;最后编写简单的测试程序进行文件下载…

[尚硅谷React笔记]——第2章 React面向组件编程

目录&#xff1a; 基本理解和使用&#xff1a; 使用React开发者工具调试函数式组件复习类的基本知识类式组件组件三大核心属性1: state 复习类中方法this指向&#xff1a; 复习bind函数&#xff1a;解决changeWeather中this指向问题&#xff1a;一般写法&#xff1a;state.htm…

毛玻璃态计算器

效果展示 页面结构组成 从上述的效果可以看出&#xff0c;计算机的页面比较规整&#xff0c;适合grid布局。 CSS3 知识点 grid 布局 实现计算机布局 <div class"container"><form class"calculator" name"calc"><input type…

【无标题】ICCV 2023 | CAPEAM:基于上下文感知规划和环境感知记忆机制构建具身智能体

文章链接&#xff1a; https://arxiv.org/abs/2308.07241 2023年&#xff0c;大型语言模型&#xff08;LLMs&#xff09;以及AI Agents的蓬勃发展为整个机器智能领域带来了全新的发展机遇。一直以来&#xff0c;研究者们对具身智能&#xff08;Embodied Artificial Intelligenc…

macOS 14 Sonoma 如何删除不需要的 4k 动态壁纸

概览 在升级到 macOS 14&#xff08;Sonoma&#xff09;之后&#xff0c;小伙伴们惊喜发现  提供了诸多高清&#xff08;4k&#xff09;动态壁纸的支持。 现在&#xff0c;从锁屏到解锁进入桌面动态到静态的切换一气呵成、无比丝滑。 壁纸显现可谓是有了“天水相连为一色&…

卷发棒上架亚马逊美国销售需要做什么认证?卷发棒UL859测试报告

卷发棒是一种美发DIY工具&#xff0c;目前美发沙龙和发廊的的美发师都会使用一套卷发棒工具。卷发棒可以造出各种卷发。如&#xff1a;大波浪卷发、下垂自然卷发、垂至肩头卷发、碎卷、麦穗烫、内翻式卷发、外翻式卷发。目前很多家庭会自己备有这样的产品DIY。 什么是UL检测报告…

脉冲法和方向盘转角法计算车辆位置不同应用工况

1 脉冲法计算车辆位置 在定义下的世界坐标系中&#xff0c;车辆运动分为右转后退、右转前进、左转后退、左转前进、直线前进、直线后退和静止七种工况&#xff0c;因此需要推倒出一组包含脉冲、车辆运动方向和车辆结构尺寸参数的综合方程式进行车辆轨迹的实时迭代计算。由于直…

源码编译tcpreplay,及使用方法

编译步骤: 下载源码 解压 ./configure make sudo make install 使用方法: tcpreplay --loop1 --intf1网卡名 -x1 pcap文件名 实测结果: 左边是输入的tcpreplay命令 右边是tcpdump截获的udp包

你熟悉Docker吗?

你熟悉Docker吗&#xff1f; 文章目录 你熟悉Docker吗&#xff1f;快速入门Docker安装1.卸载旧版2.配置Docker的yum库3.安装Docker4.启动和校验5.配置镜像加速5.1.注册阿里云账号5.2.开通镜像服务5.3.配置镜像加速 部署MySQL镜像和容器命令解读 Docker基础常用命令数据卷数据卷…

Linux常见指令(1)

Linux常见指令[1] 一.前言1.操作系统简述 二.Linux常见指令1.登录Xshell2.Linux下的常见命令1.pwd2.ls1.ls -a2.ls -d3.ls -l 3.cd Linux中的文件系统1.文件的相关知识2.Linux下目录结构的认识1.什么叫做路径?2.Linux的整体目录结构3.为什么要有路径呢?4.绝对路径与相对路径 …

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

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

队列的各个函数的实现

1.第一个结构是存放链表的数据&#xff0c;第二个结构体是存放头节点和尾节点的以方便找到尾节点&#xff0c;存放头节点的是phead&#xff0c;尾节点的是ptail typedef struct QueueNode {struct QueueNode* next;//单链表QDataType data;//放数据 }QNode;typedef struct Queu…