@Aysnc注解其实也就这么回事! (下)

简介: @Aysnc注解其实也就这么回事! (下)

返回类型的支持


前面我们卷完了第一个关于配置的问题。

接下来,我们看另外一个前面提出的问题:

源码是怎么做到只支持 void 和 Future 的?

答案就藏在这个方法里面:

org.springframework.aop.interceptor.AsyncExecutionInterceptor#invoke


image.png


标号为 ① 的地方,其实就是我们前面分析的从 map 里面拿 method 对应的线程池的方法。

拿到线程池之后来到标号为 ② 的地方,就是封装一个 Callable 对象。

那么是把什么封装到 Callable 对象里面呢?

这个问题先按下不表,我们先牢牢的围绕我们的问题往下走,不然问题会越来越多。

标号为 ③ 的地方,doSubmit,见名知意,这个地方就是执行任务的地方了。

org.springframework.aop.interceptor.AsyncExecutionAspectSupport#doSubmit


image.png


其实这里就是我要找的答案。

你看这个方法的入参 returnType 是 String,其实就是被 @Async 注解修饰的 asyncSay 方法。

你要不信,我可以带你看看前一个调用栈,这里可以看到具体的方法:


image.png

怎么样,没有骗你吧。

所以,现在你再看 doSubmit 方法拿着这个方法的返回类型干啥了。

一共四个分支,前面三个都是判断是否是 Future 类型的。

其中的 ListenableFuture 和 CompletableFuture 都是继承自 Future 的。

这个两个类在 @Async 注解的方法注释里面也提到了:

image.png

而我们的程序走到了最后的一个 else,含义就是返回值不是 Future 类型的。

那么你看它干了啥事儿?

image.png

直接把任务 submit 到线程池之后,就返回了一个 null。

这可不得爆出空指针异常吗?

到这个地方,我们也解决了这个问题:

源码是怎么做到只支持 void 和 Future 的?

其实道理很简单,我们正常的使用线程池提交不也就这两个返回类型吗?

用 submit 的方式提交,返回一个 Future,把结果封装到 Future 里面:

image.png


而框架通过一个简单的注解帮我们实现异步化,它玩的再花里胡哨 ,就算是玩出花来了,它也得遵守线程池提交的底层原理啊。

所以,源码为什么只支持 void 和 Future 的返回类型?

因为底层的线程池只支持这两种类型的返回。

只是它的做法稍微有点坑,直接把其他的返回类型的返回值都处理为 null 了。

你还别不服,谁叫你不读注释上的说明呀。

另外,我发现这个地方还有个小的优化点:

image.png


当它走到这个方法的时候,返回值已经明确是 null 了。

为什么还用 executor.submit(task) 提交任务呢?

用 execute 就行了啊。

区别,你问我区别?

不是刚刚才说了吗, submit 方法是有返回值的。


image.png


虽然你不用,但是它还是会去构建一个返回的 Future 对象呀。

然而构建出来了,也没用上呀。

所以直接用 execute 提交就行了。

少生成一个 Future 对象,算不算优化?

有一说一,不算什么有价值的优化,但是说出去可是优化过 Spring 的源码的,装逼够用了。


image.png


接着,再说一下我们前面按下不表的部分,这里编号为 ② 的地方封装的到底是什么?


image.png


其实这个问题用脚指头应该也猜到了:


image.png


只是我单独拧出来说的原因是我要给你证明,这里返回的 result 就是我们方法返回的真实的值。

只是判断了一下类型不是 Future 的话就不做处理,比如我这里其实是返回了 hi:1 字符串的,只是不符合条件,就被扔掉了:


image.png


image.png

对于为什么要这么改,现在我们已经拿捏的非常清楚了。

知其然,也知其所以然。


image.png


@Async 注解的 value


接下来我们看看 @Async 注解的 value 属性是干什么的。

其实在前面我已经悄悄的提到了,只是一句话就带过了,就是这个地方:


image.png

前面说编号为 ① 的地方,是获取对应方法上的 @Async 注解的 value 值。这个值其实就是 bean 名称,如果不为空则从 Spring 容器中获取对应的 bean。

然后我就直接分析到标号为 ② 的地方了。

现在我们重新看看标号为 ① 的地方。

我也重新安排一个测试用例去验证我的想法。

反正 value 值应该是 Spring 的 bean 名称,而且这个 bean 一定是一个线程池对象,这个没啥说的。

所以,我把 Demo 程序修改为这样:

image.png

再次跑起来,跑到这个断点的地方,就和我们默认的情况不一样了,这个时候 qualifier 有值了:

image.png

接下来就是去 beanFactory 里面拿名字为 whyThreadPool 的 bean 了。

最后,拿出来的线程池就是我自定义的这个线程池:

image.png


这个其实是一个很简单的探索过程,但是这背后蕴涵了一个道理。

就是之前有同学问我的这个问题:


image.png


其实这个问题挺有代表性的,很多同学都认为线程池不能滥用,一个项目共用一个就好了。

线程池确实不能滥用,但是一个项目里面确实是可以有多个自定义线程池的。

根据你的业务场景来划分。

比如举个简单的例子,业务主流程上可以用一个线程池,但是当主流程中的某个环节出问题了,假设需要发送预警短信。

发送预警短信的这个操作,就可以用另外一个线程池来做。

它们可以共用一个线程池吗?

可以,能用。

但是会出现什么问题呢?

假设项目中某个业务出问题了,在不断的,疯狂的发送预警短信,甚至把线程池都占满了。

这个时候如果主流程的业务和发送短信用的是同一个线程池,会出现什么美丽的场景?

是不是一提交任务,就直接走到拒绝策略里面去了?

预警短信发送这个附属功能,导致了业务不可以,本末倒置的了吧?

所以,建议使用两个不同的线程池,各司其职。

这其实就是听起来很高大上的线程池隔离技术。

那么落到 @Async 注解上是怎么回事呢?

其实就是这样的:

image.png

然后,还记得我们前面提到的那个维护方法和线程池的映射关系的 map 吗?

就是它:

image.png

看明白了吗?

再次复述一次这句话:

以方法维度维护方法和线程池之间的关系。

现在,我对于 @Async 这个注解算是有了一点点的了解,我觉得它也还是很可爱的。后面也许我会考虑在项目里面把它给用起来。毕竟它更加符合 SpringBoot 的基于注解开发的编程理念。


最后说一句


好了,看到了这里了,点赞、关注随便安排一个吧,要是你都安排上我也不介意。写文章很累的,需要一点正反馈。

给各位读者朋友们磕一个了:

微信图片_20220428162928.jpg

目录
相关文章
|
5月前
|
监控 Java Spring
AOP切入同类调用方法不起作用,AopContext.currentProxy()帮你解决这个坑
AOP切入同类调用方法不起作用,AopContext.currentProxy()帮你解决这个坑
301 1
|
7月前
|
Java
SpringAop切面编程(一看就会用)
SpringAop切面编程(一看就会用)
29 0
|
7月前
|
监控 安全 Java
Spring注解之恋:@Async和@Transactional的双重奏
Spring注解之恋:@Async和@Transactional的双重奏
965 0
|
7月前
|
Java
一行注解,省却百行代码:深度解析@RequiredArgsConstructor的妙用
一行注解,省却百行代码:深度解析@RequiredArgsConstructor的妙用
475 0
|
Java Spring 容器
Spring源码:Bean生命周期(终章)
本系列前面讲解了Spring的bean定义、bean实例化、bean初始化等生命周期阶段。这些步骤使我们能够了解bean从创建到准备好使用所经历的过程。但是,除了这些步骤,bean的销毁也是非常重要的一步。在本系列的最后,我们将深入探讨bean的销毁过程,包括在什么情况下会发生销毁、销毁的顺序以及如何在bean销毁之前执行一些清理任务等。通过学习bean的销毁过程,我们将更全面地了解Spring的bean生命周期。在Spring中,有多种方式可以销毁bean。其中一种方式是在应用程序关闭时显式地调用`applicationContext.close()`方法来关闭容器。这个方法将会销毁所有还没
|
设计模式 Java 数据库连接
搞懂钩子方法和模板方法,看完这篇就够了
通常的模板方法模式中会设计一个abstract的抽象方法,交给它的子类实现,这个方法称为模板方法。而钩子方法,是对于抽象方法或者接口中定义的方法的一个空实现,也是模板方法模式的一种实现方式。
170 0
|
Java Spring
SpringBean初始化顺序
SpringBean初始化顺序
70 1
|
Java
【Java面向对象】方法重载,方法重写你还傻傻弄不清吗?
【Java面向对象】方法重载,方法重写你还傻傻弄不清吗?
97 0
|
设计模式 安全 Java
java设计模式之单例设计模式的妙用
1.设计模式 设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美地解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因
89 1
|
存储 SQL Java
聊一聊使用事务时(@Transactional)可能出现的问题
聊一聊使用事务时(@Transactional)可能出现的问题
339 0
聊一聊使用事务时(@Transactional)可能出现的问题