@Async源码解析,从注释开始讲解

本文深入探讨了Spring的@Async注解,讲解了其在异步方法调用中的应用,包括启用条件、使用方式和自定义线程池。通过源码分析,揭示了@Async注解如何通过BeanPostProcessor实现AOP代理,创建切面和方法拦截器,以及线程池的选取过程。此外,文章还提到了使用@Async时的注意事项和常见陷阱,如异步方法内部调用的同步问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

前言

定义

使用方式

源码解析

1.1 Async注解

1.2 抽象Bean处理器

1.3 切面,构建advisor

1.4 advice方法拦截器

1.5 创建切入点

1.6 回到抽象Bean处理器

总结


前言

本篇会涉及到@Async的使用场景,工作中遇到的坑。以及底层源码的详细分析。

定义

@Async注解是spring的一个用于调用方法时异步的注解。也就是方法的异步调用,当一个业务方法被调用时,主线程不需要它马上返回结果,可以丢到一边让他自己执行的时候,@Async注解便派上了用场。

使用方式

相信很多小伙伴工作中都使用过@Async这个注解,或许没有使用过也见到过。这是一个非常常见的注解。它的使用方式也是特别简单,只需要在需要异步执行的方法上加上这个注解就可以了。

需要使用它,首先你需要在springboot启动类上加上@EnableAsync,这非常重要,因为你不加它不会生效(因为这个注解的作用是初始化Aysnc的一些配置,这个后面源码里讲)。

然后它大概就是这种姿势

@Async 
public void simpleAsync(){
     log.info("{}-线程执行", Thread.currentThread().getName()); 
}

调用这个方法

@Test 
public void simpleAsyncTest(){
     log.info("{}-线程执行", Thread.currentThread().getName()); asyncDemo.simpleAsync(); 
}

查看执行结果,果然是一个线程单独在跑,是异步的没有问题。

注意到这里异步方法执行的时候有一个SimpleAsyncTaskExecutor,一看这个名字就像是线程池一样的东西。其实这个东西不算是一个线程池,使用这个Executor只是会新创建一条新的线程来执行你的方法,有兴趣的可以点进去看看它的源码。

如果你不指定线程池,那么它就会默认使用这个伪线程池。但是在工作中肯定是使用自己创建的线程池呀,这样也可控一点。当然有默认就会给你使用自己线程池的方法。

使用自己定义的线程池也比较简单,只需要在@Async注解里面传入自己定义的线程池的beanName就可以了,就下面这样:

先定义一个线程池:

@Bean("specificThreadPool")

public ThreadPoolTaskExecutor createSpecificThreadPool() {

ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();

threadPoolTaskExecutor.setCorePoolSize(6);

threadPoolTaskExecutor.setMaxPoolSize(20);

threadPoolTaskExecutor.setKeepAliveSeconds(60);

threadPoolTaskExecutor.setQueueCapacity(Integer.MAX_VALUE);

threadPoolTaskExecutor.setThreadNamePrefix("specificThreadPool-");

threadPoolTaskExecutor.initialize();

return threadPoolTaskExecutor;

}

然后再将这个线程池名称放在注解里面,这样这个异步方法使用线程池就是我们自己定义的线程池啦。

@Async("commonThreadPool")

public CompletableFuture<String> testAsync01(String str) {

    log.info("{}-线程执行", Thread.currentThread().getName());

    return CompletableFuture.completedFuture(str);

}

值得注意的事@Async注解不仅可以定义在方法上,还可以定义在类上,没错,在类上使用该注解,整个类里面的方法都会被代理,变成异步的方法。

以下是使用这个注解需要注意的坑点,在同一类里面,同步方法调用异步方法,那么异步方法是不会生效的。

为什么?这个放到源码解析的结尾说吧,这里先卖个关子(主要了解了源码后才会对各种情况了若指掌)。

调用方式

同一类

结论

同步->异步

不生效

异步->异步

生效,使用调用方法的线程池

同步->异步

生效

异步->异步

生效,使用方法各自的线程池

源码解析

好了在了解了@Async注解的基本使用方法后,终于到激动人心的源码部分了。

1.1 Async注解

那就先从这个注解本身入手去看一下。注解上面有一大段注释,我们挨个大概看一下。

上面第一段大概意思就是,这个注解可以使用在方法上,也可以使用在类上,它这里的type level应该就是类级别的意思,然后,它不能和@Configuration注解一起使用。

第二段的注解比较有意思,它这里说使用@Async注解的方法,只允许返回void或者Future类,然后这个Future类可以是ListenableFuture也可以是CompletableFuture,那么是不是这样呢?各位可以试一下,笔者反正试过,如果是直接返回一个String或者是Integer那么返回值会是空。这里先记下来,后面咱们深入源码的时候再找为什么。

然后再简单看一下注解里面value值上面的注解,他这里大概意思就是说,你可以指定线程池的名称,然后@Async注解会使用这些注解,如果你将注解定义到类上,那么这个类里面的所有方法都会使用这个线程池。

其实,这些注释就已经非常完整的告诉了我们,@Async注解到底应该怎样使用了。

还记得使用@Async注解需要先引入什么注解吗?对,就是@EnableAsync。一般加在启动类加载的注解,都会初始化一些东西,所以我们从这个注解开始入手。

这个注解引入了一个AsyncConfigurationSelector,点进去,这个类继承了AdviceModeImportSelector类,而类又实现了ImportSelector这个就是和@Import搭配使用的,不了解伙伴可以自行百度一下,这里做了一下判断,返回了一个ProxyAsyncConfiguration类。我们点进这个类去看一下。

发现这是一个配置类,这个配置类初始化了一个AsyncAnnotationBeanPostProcessor对象。继续进去研究一下这个对象。

1.2 抽象Bean处理器

我们先看一下这个AsyncAnnotationBeanPostProcessor类的继承和实现关系图如下所示,从这张图我们可以看到,它是继承了两个抽象类,并且,它的父亲实现了BeanFactoryAware,而他的爷爷实现了BeanPostProcessor。看到这两个接口我们可以大胆的猜想一下,BeanFactoryAware实现是可以拿到beanFactory然后做一些事情,而BeanPostProcessor更是被很多源码广泛应用,通过实现它可以在Bean初始化的前后对bean做处理,都知道@Async应该是基于Aop做的,而这里会不会是在Bean初始化的时候去做的增强呢?带着疑问,继续往下深究。

进入AsyncAnnotationBeanPostProcessor类,既然是实现了BeanFactoryAware那么肯定是会实现setBeanFactroy方法啦,首先找到这个方法看一下。

发现这里是去new了一个AsyncAnnotationAdvisor对象,进入这个类里瞧一眼先。

1.3 切面,构建advisor

老规矩先把类的关系理顺才有利于整个功能的剖析,发现这个类继承了一个抽象类AbstractPointcutAdvisor看这个类名称又是pointcut又是advisor的,肯定是aop相关的呀。而且这个抽象类是实现了advisor,这是一个容器,将pointcut与通知结合形成切面的容器。

理清楚关系之后,我们再进入AsyncAnnotationAdvisor类看一下,发现这个类有两个属性一个是advice一个是pointcut,这都是老朋友了,一个是通知一个则是切入点,然后就可以看构造方法了,这里我们可以看到,advice由buildAdvice创建,pointcut由buildPointcut创建。

我们就先从这个通知的创建入手,点击进入buildAdvice方法,这个方法是去创建了一个AnnotationAsyncExecutionInterceptor对象。

1.4 advice方法拦截器

这个对象的继承关系图如下所示,我们可以看到这个类的父亲是实现了一个方法的拦截器接口,MethodInterceptor。那好,就进入他的父类看看。

父类重写的invoke方法就是@Async实现最核心的方法了!经过千辛万苦一步一个脚印,终于是来到了这一步。

那么肯定需要仔细的阅读一下这部分代码了,首先它这里是先去拿了异步调用所需要的线程池通过determineAsyncExecutor方法

而这个方法里面是先去拿一个qualifier,这个方法其实就是去拿你写在@Async注解里面的value的值,可以点进去跟一下这个方法,发现果然是这样的。

好,回到determineAsyncExecutor方法,这里代码就比较简单易懂了,大概就是如果你指定了线程池的名称,他就通过beanFactory去拿对应名称的线程池的bean,如果你没有制定,他就去拿默认的线程池。

通过determineAsyncExecutor拿到线程池后,我们回到invoke方法,继续阅读他的方法,它这里是new了一个Callable,也就是多线程的常规操作,然后里面做了一个判断,如果返回值是Futrue类型,则将方法执行的结果返回,否则没有做任何处理,只是去执行方法。还记得我们之前阅读@Async注解上面的注释说的吗?他说他只支持Future类型以及void,这里就真的是说到做到了,哈哈。

创建了Callable后就调用doSubmit方法,这个方法里面就是多个if判断是不是future类,如果不是的话,就直接由线程池提交任务了,然后这里是直接返回一个null。这里也印证了,被@Async注解修饰的方法,只支持Future和void类型,如果强行使用其他类型的返回值,那就会返回null。

以上就是buildAdvice的创建过程啦,其实也就是创建了个方法拦截器,对方法进行增强,也就是通过线程池拿一条线程过来,然后执行@Async修饰的方法。

1.5 创建切入点

继续回到构造方法,我们现在已经知道了通知是怎么创建的了,那么现在来看一下buildPointcut,看看他是如何创建切入点的。这里是丢进去了一个Set集合,这个集合放入的是Async注解。

然后这个方法就是创建切入点的实现。

创建完成后,整个advisor就构建完成了,我们先得回到最初我们所说的AsyncAnnotationBeanPostProcessor这个bean处理器了。

1.6 回到抽象Bean处理器

深入看了一圈之后,有的小伙伴我相信已经彻底蒙圈,醒醒,该回来了!如果忘了也别急回头看下1.2的类构建关系,我们现在需要回到AsyncAnnotationBeanPostProcessor,这里将上面所说到的advisor给创建好之后就赋值给了当前类的advisor属性。

还记得我们之前做过猜想吗?1.2的时候,猜想是通过aop对bean处理进行增强,我们的猜想正在一步步印证。观察1.2章给出的类关系图,发现是AbstractAdvisingBeanPostProcessor类实现了BeanPostProcessor,到这里,算是彻底破案了。和我们的想法一模一样,这里postProcessAfterInitialization方法,在bean实例化之后,通过ProxyFactory创建出一个代理对象返回。而这个代理对象的Advisor也正是当前这个类的Advisor,也就是之前我们分析的通过AsyncAnnotationAdvisor类。

自此,整个代理过程全部结束,整个@Async的源码分析也到此结束啦,完结,撒花!

总结

其实整个@Async注解的源码不算特别难,是各种抽象类继承接口实现,让我们阅读起来比较麻烦,但是多看几遍,应该都是没有问题的。整个核心代码说实话也不难,要咱们写也能写出来,哈哈!不过整个方法实现的过程曲曲折折。不过我相信多看源码,多理解大佬们的思想,有朝一日,自己也能变得很强,加油,打工人!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值