@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

目录
相关文章
|
6月前
|
监控 安全 Java
Spring注解之恋:@Async和@Transactional的双重奏
Spring注解之恋:@Async和@Transactional的双重奏
855 0
|
Java 测试技术 Spring
@Async注解 -- 异步调用的万金油
@Async注解 -- 异步调用几乎是处理高并发Web应用性能问题的万金油.那么什么是“异步调用”?“异步调用”对应的是“同步调用”,同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行;异步调用指程序在顺序执行时,不等待异步调用的语句返回结果就执行后面的程序。
85 1
|
JSON 数据格式
@JsonProperty与@JSONField注解用处
@JsonProperty与@JSONField注解用处
|
Java Spring 容器
|
JSON 前端开发 Java
|
JSON API PHP
【源码解读】TP框架参数注入,参数绑定
核心是:使用反射类,拿到需要执行的类、方法属性,然后分析传参的属性,在post、get、类属性等等参数中,按不同优先级搜寻符合注入条件的参数。 最终使用执行,并且提供组装正确的参数数组。 php的反射类,可以分析目标类的各种属性 方法列表、参数、私有共有属性、方法的类型等等 以下提供一个简单的列表
273 0
|
Java 中间件 程序员
@Aysnc注解其实也就这么回事! (中)
@Aysnc注解其实也就这么回事! (中)
283 0
@Aysnc注解其实也就这么回事! (中)
|
Java 测试技术 应用服务中间件
@Aysnc注解其实也就这么回事! (上)
@Aysnc注解其实也就这么回事! (上)
230 0
@Aysnc注解其实也就这么回事! (上)
|
监控 前端开发 Java
过滤器与拦截器的N个区别,别傻傻分不清了
过滤器与拦截器的N个区别,别傻傻分不清了
227 0
过滤器与拦截器的N个区别,别傻傻分不清了