Async 注解底层异步线程

news/2024/9/26 3:19:49

一、前言

开发中我们经常会用到异步方法调用,具体到代码层面,异步方法调用的实现方式有很多种,比如最原始的通过实现 Runnable 接口或者继承 Thread 类创建异步线程,然后启动异步线程;再如,可以直接用 java.util.concurrent 包提供的线程池相关 API 实现异步方法调用。

如果说可以用一行代码快速实现异步方法调用,那是不是比上面方法香很多。

Spring 提供了 Async 注解,就可以帮助我们一行代码搞定异步方法调用。Async 注解用起来是很爽,但是如果不对其底层实现做深入研究,难免有时候也会心生疑虑,甚至会因使用不当,遇见一些让人摸不着头脑的问题。

本文首先将对 Async 注解做简单介绍,然后和大家分享一个我们项目中因 Async 注解使用不当的线上问题,接着再深扒 Spring 源码,对 Async 注解底层异步线程池的实现原理一探究竟。

二、Async 注解简介

Async 注解定义源码

001.jpg

从源码可以看出 @Async 注解定义很简单,只需要关注两点:

  • Target ({ElementType.TYPE, ElementType.METHOD}) 标志 Async 注解可以作用在方法和类上,作用在类上时,类的所有方法可以实现异步调用。

  • String value ( ) default "" 是唯一字段属性,用来指定异步线程池,且该字段有缺省值。

Async 注解异步调用实现原理概述

在 Spring 框架中,Async 注解的实现是通过 AOP 来实现的。具体来说,Async 注解是由 AsyncAnnotationAdvisor 这个切面类来实现的。

AsyncAnnotationAdvisor 类是 Spring 框架中用于处理 Async 注解的切面,它会在被 Async 注解标识的方法被调用时,创建一个异步代理对象来执行方法 。这个异步代理对象会在一个新的线程中调用被 @Async 注解标识的方法,从而实现方法的异步执行。

在 AsyncAnnotationAdvisor 中,会使用 AsyncExecutionInterceptor 来处理 Async 注解。AsyncExecutionInterceptor 是实现了 MethodInterceptor 接口的类,用于拦截被 Async 注解标识的方法的调用,并在一个新的线程中执行这个方法。

通过 AOP 的方式实现 Async 注解的异步执行,Spring 框架可以在方法调用时动态地创建代理对象来实现异步执行,而不需要在业务代码中显式地创建新线程。

总的来说,Async 注解的实现是通过 AOP 机制来实现的,具体的切面类是 AsyncAnnotationAdvisor,它利用 AsyncExecutionInterceptor 来处理被 Async 注解标识的方法的调用,实现方法的异步执行。

三、Async 注解底层异步线程池原理探究

获取 Async 注解线程池主流程解析

进入到 Spring 源码 Async 注解 AOP 切面实现部分,我们重点剖析异步调用实现中线程池是怎么处理的。下图是 org.springframework.aop.interceptor.AsyncExecutionInterceptor#invoke 方法的实现,可以看出是调用 determineAsyncExecutor 方法获取异步线程池。 002.jpg AsyncExecutionInterceptor#invoke

下图是 determineAsyncExecutor 方法实现: 003.jpg

004.jpg

上方的图为 AsyncExecutionInterceptor#determineAsyncExecutor,下方的图为 AsyncExecutionAspectSupport#getExecutorQualifier

从代码实现中可以看到 determineAsyncExecutor 获取线程池的大致流程: 005.jpg determineAsyncExecutor 获取线程池流程

如果在使用 Async 注解时指定了自定义线程池比较好理解,如果使用 Async 注解时没有指定自定义线程池,Spring 是怎么处理默认线程池呢?继续深入源码看看 Spring 提供的默认线程池的实现。

Spring 是怎么为 Async 注解提供默认线程池的

Async 注解默认线程池有下面两个方法实现:

  • org.springframework.aop.interceptor.AsyncExecutionInterceptor#getDefaultExecutor

  • org.springframework.aop.interceptor.AsyncExecutionAspectSupport#getDefaultExecutor

006.jpg AsyncExecutionInterceptor#getDefaultExecutor

可以看出 AsyncExecutionInterceptor#getDefaultExecutor 方法比较简单:先尝试调用父类 AsyncExecutionAspectSupport#getDefaultExecutor 方法获取线程池,如果父类方法获取不到线程池再用创建 SimpleAsyncTaskExecutor 对象作为 Async 的线程池返回。

007.jpg AsyncExecutionAspectSupport#getDefaultExecutor

再来看父类 AsyncExecutionAspectSupport#getDefaultExecutor 方法的实现,可以看到 Spring 根据类型从 Spring 容器中获取 TaskExecutor 类的实例,先记住这个关键点

我们知道,Spring 根据类型获取实例时,如果 spring 容器中有且只有一个指定类型的实例对象,会直接返回,否则的话,会抛出 NoUniqueBeanDefinitionException 异常或者 NoSuchBeanDefinitionException 异常。

但是,对于 Executor 类型,Spring 容器却 "网开一面",有一个特殊处理:当从 Spring 容器中获取 Executor 实例对象时,如果满足 @ConditionalOnMissingBean (Executor.class) 条件,Spring 容器会自动装载一个 ThreadPoolTaskExecutor 实例对象,而 ThreadPoolTaskExecutor 是 TaskExecutor 的实现类

008.jpg

009.jpg

上方的图为 TaskExecutionAutoConfiguration,下方的图为 TaskExecutionProperties

从 TaskExecutionProperties 和 TaskExecutionAutoConfiguration 两个配置类我们看到 Spring 自动装载的 ThreadPoolTaskExecutor 线程池对象的参数:核心线程数 = 8;最大线程数 = Integer.MAX_VALUE;队列大小 = Integer.MAX_VALUE。

四、总结

现在 Async 注解线程池源码已经看的差不多了,下面这张图是 Spring 处理 Async 异步线程池的流程: 100.jpg Async 异步线程池获取流程

归纳一下:如果在使用 Async 注解时没有指定自定义的线程池会出现以下几种情况:

  • 当 Spring 容器中有且仅有一个 TaskExecutor 实例时,Spring 会用这个线程池来处理 Async 注解的异步任务,这可能会踩坑,如果这个 TaskExecutor 实例是第三方 jar 引入的,可能会出现很诡异的问题。

  • Spring 创建一个核心线程数 = 8、最大线程数 = Integer.MAX_VALUE、队列大小 = Integer.MAX_VALUE 的线程池 来处理 Async 注解的异步任务,这时候也可能会踩坑,由于线程池参数设置不合理,核心线程数 = 8,队列大小过大,如果有大批量并发任务,可能会出现 OOM

  • Spring 创建 SimpleAsyncTaskExecutor 实例 来处理 Async 注解的异步任务,SimpleAsyncTaskExecutor 不是一个好的线程池实现类,SimpleAsyncTaskExecutor 根据需要在当前线程或者新线程中执行异步任务。如果当前线程已经有空闲线程可用,任务将在当前线程中执行,否则将创建一个新线程来执行任务。由于这个线程池没有线程管理的能力,每次提交任务都实时创建新城,所以如果任务量大,会导致性能下降

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

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

相关文章

dedecms图集dede:field name=imgurls不能二次使用的解决办法

{dede:field name=imgurls alt=图片输出区}图片链接 [field:linkurl/]图片地址 [field:imgsrc/]{/dede:field}这个标签不能同时使用2次,所以第二次的话用!!!{dede:productimagelist}图片链接 [field:linkurl/]图片地址 [field:imgsrc/]{/dede:productimagelist}<div id=&…

IntelliJ IDEA 2024 mac/win版:编程利器,智慧之选

IntelliJ IDEA 2024是一款由JetBrains精心打造的集成开发环境(IDE),专为Java等编程语言量身打造,同时支持多种其他语言,为开发者提供了卓越的开发体验。 IntelliJ IDEA 2024 mac/win版获取这款IDE凭借其出色的智能化和高效性,赢得了广大开发者的喜爱。IDEA 2024不仅提供了丰…

JAX-中文文档-八-

JAX 中文文档(八)原文:jax.readthedocs.io/en/latest/自动微分手册原文:jax.readthedocs.io/en/latest/notebooks/autodiff_cookbook.htmlalexbw@, mattjj@ JAX 拥有非常通用的自动微分系统。在这本手册中,我们将介绍许多巧妙的自动微分思想,您可以根据自己的工作进行选择…

KVM虚拟机安装部署全攻略 cockpit

01 原理KVM(Kernel-based Virtual Machine)虚拟化技术是一种基于内核的虚拟化技术,KVM虚拟化技术的实现依赖于CPU的虚拟化扩展(如Intel VT和AMD-V)。当宿主机启动时,KVM会加载一个轻量级的内核模块kvm.ko,该模块负责与硬件进行交互,实现虚拟机的创建、管理和调度。02 组…

DC/AC电源模块:提高太阳能发电系统的效率和稳定性

BOSHIDA DC/AC电源模块:提高太阳能发电系统的效率和稳定性 DC/AC电源模块是太阳能发电系统中的一个重要组成部分,其作用是将太阳能转化为交流电以供家庭或工业使用。它可以提高太阳能发电系统的效率和稳定性,使得太阳能发电系统更加可靠和持久。 一,DC/AC电源模块可以提高…

synchronized 和 ReentrantLock (Lock)区别,优劣对比

synchronized 和 ReentrantLock (Lock)区别两种方法都是为了确保多线程环境中的线程安全,但它们使用了不同的同步机制:synchronized 关键字和 Lock 接口。下面详细对比这两种方法的区别、优缺点以及适用场景。 synchronized 关键字 public synchronized void addSession(Http…

振弦采集仪在桥梁工程中的应用与发展趋势

振弦采集仪在桥梁工程中的应用与发展趋势 振弦采集仪作为一种精密的传感器测量设备,在桥梁工程中扮演着至关重要的角色,它通过监测桥梁结构的振动频率和弦张力变化来评估结构的健康状况和承载能力。随着桥梁工程技术的不断进步和智能化趋势的发展,振弦采集仪的应用日益广泛,…

验证码的识别和运用

验证码的主要目的是强制人机交互来抵御机器自动化攻击,为了确保服务器系统的稳定和用户信息的安全,越来越多的网站采用了验证码技术。图片验证码是目前最常用的一种,本文也主要讨论这种验证码的识别。最近在一个爬虫项目中遇到了验证码,需要机器自动识别绕过。这些验证码大…