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

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: @Aysnc注解其实也就这么回事! (上)

你好呀,我是why。

我之前写过一些关于线程池的文章,然后有同学去翻了一圈,发现我没有写过一篇关于 @Async 注解的文章,于是他来问我:


image.png


是的,我摊牌了。

我不喜欢这个注解的原因,是因为我压根就没用过。


image.png


我习惯用自定义线程池的方式去做一些异步的逻辑,且这么多年一直都是这样用的。

所以如果是我主导的项目,你在项目里面肯定是看不到 @Async 注解的。

那我之前见过 @Async 注解吗?

肯定是见过啊,有的朋友就喜欢用这个注解。

一个注解就搞定异步开发,多爽啊。

我不知道用这个注解的人知不知道其原理,反正我是不知道的。

最近开发的时候引入了一个组件,发现调用的方法里面,有的地方用到了这个注解。

既然这次用到了,那就研究一下吧。

首先需要说明的是,本文并不会写线程池相关的知识点。

仅描述我是通过什么方式,去了解这个我之前一无所知的注解的。


搞个 Demo


不知道大家如果碰到这种情况会去怎么下手啊。

但是我认为不论是从什么角度去下手的,最后一定是会落到源码里面的。

所以,我一般是先搞个 Demo。

Demo 非常简单啊,就三个类。

首先是启动类,这没啥说的:


image.png


这个 service 里面的 syncSay 方法被打上了 @Async 注解。

最后,搞个 Controller 来调用它,完事:


image.png


Demo 就搭建好了,你也动手去搞一个,耗时超过 5 分钟,算我输。

然后,把项目启动起来,调用接口,查看日志:


image.png


我去,从线程名称来看,这也没异步呀?

怎么还是 tomcat 的线程呢?

于是,我就遇到了研究路上的第一个问题:@Async 注解没有生效。


为啥不生效?


为什么不生效呢?

我也是懵逼的,我说了之前对这个注解一无所知,那我怎么知道呢?

那遇到这个问题的时候会怎么办?

当然是面向浏览器编程啦!

这个地方,如果我自己从源码里面去分析为啥没生效,一定也能查出原因。

但是,如果我面向浏览器编程,只需要 30 秒,我就能查到这两个信息:

失效原因:

  • 1.@SpringBootApplication 启动类当中没有添加 @EnableAsync 注解。
  • 2.没有走 Spring 的代理类。因为 @Transactional@Async 注解的实现都是基于 Spring 的 AOP,而 AOP 的实现是基于动态代理模式实现的。那么注解失效的原因就很明显了,有可能因为调用方法的是对象本身而不是代理对象,因为没有经过 Spring 容器管理。

很显然,我这个情况符合第一种情况,没有添加 @EnableAsync 注解。

另外一个原因,我也很感兴趣,但是现在我的首要任务是把 Demo 搭建好,所以不能被其他信息给诱惑了。

很多同学带着问题去查询的时候,本来查的问题是@Async 注解为什么没有生效,结果慢慢的就走偏了,十五分钟后问题就逐渐演变为了 SpringBoot 的启动流程。

再过半小时,网页上就显示的是一些面试必背八股文之类的东西...

我说这个意思就是,查问题就好好查问题。查问题的过程中肯定会由这个问题引发的自己更加感兴趣的问题。但是,记录下来,先不要让问题发散。

这个道理,就和带着问题去看源码一样,看着看着,可能连自己的问题是什么都不知道了。

image.png


可以看到线程名字变了,说明真的就好了。

现在我的 Demo 已经搭好了,可以开始找角度去卷了。

从上面的日志我也能知道,在默认情况下有一个线程前缀为 task- 的线程池在帮我执行任务。

说到线程池,我就得知道这个线程池的相关配置才放心。

那么我怎么才能知道呢?


先压一压


其实正常人的思路这个时候就应该是去翻源码,找对应的注入线程池的地方。

而我,就有点不正常了,我懒得去源码里面找,我想让它自己暴露到我的面前。

怎么让它暴露出来呢?

仗着我对线程池的了解,我的第一个思路是先压一压这个线程池。

压爆它,压的它处理不过来任务,让它走到拒绝逻辑里面去,正常来说是会抛出异常的吧?


image.png

于是,我把程序稍微改造了一下:

image.png

想的是直接来一波大力出奇迹:

image.png

结果...

它竟然...

照单全收了,没有异常?

日志一秒打几行,打的很欢乐:

image.png

虽然没有出现我预想的拒绝异常,但是我从日志里面还是看出了一点点端倪。

比如我就发现这个 taks 最多就到 8:

image.png

朋友们,你说这是啥意思?

是不是就是说这个我正在寻找的线程池的核心线程数的配置是 8 ?

什么,你问我为什么不能是最大线程数?

有可能吗?

当然有可能。但是我 10000 个任务发过来,没有触发线程池拒绝策略,刚好把最大线程池给用完了?

也就是说这个线程池的配置是队列长度 9992,最大线程数 8 ?

这也太巧合了且不合理了吧?

所以我觉得核心线程数配置是 8 ,队列长度应该是 Integer.MAX_VALUE

为了证实我的猜想,我把请求改成了这样:

image.png

那叫一个飙升啊,点击【执行GC】按钮也没有任何缓解。

也从侧面证明了:任务有可能都进队列里面排队了,导致内存飙升。

虽然,我现在还不知道它的配置是什么,但是经过刚刚的黑盒测试,我有正当的理由怀疑:

默认的线程池有导致内存溢出的风险。

image.png


但是,同时也意味着我想从让它抛出异常,从而自己暴露在我面前的骚想法落空。


怼源码


前面的思路走不通,老老实实的开始怼源码吧。

我是从这个注解开始怼的:


image.png


主要关注我画线的地方。

In terms of target method signatures, any parameter types are supported.

在目标方法的签名中,入参是任何类型都支持的。

多说一句:这里说到目标方法,说到 target,大家脑海里面应该是要立刻出现一个代理对象的概念的。

上面这句话好理解,甚至感觉是一句废话。

但是,它紧跟了一个 However:

However, the return type is constrained to either void or Future.


image.png


constrained,受限制,被约束的意思。

这句话是说:返回类型被限制为 void 或者 Future。

啥意思呢?

那我偏要返回一个 String 呢?

image.png


WTF,打印出来的居然是 null !?


image.png


那这里如果我返回一个对象,岂不是很容易爆出空指针异常?

看完注解上的注释之后,我发现了第二个隐藏的坑:

如果被 @Async 注解修饰的方法,返回值只能是 void 或者 Future。

void 就不说了,说说这个 Future。

看我划线的另外一句:

it will have to return a temporary {@code Future} handle that just passes a value through: e.g. Spring's {@link AsyncResult}

上有一个 temporary,是四级词汇啊,应该认识的,就是短暂的、暂时的意思。

temporary worker,临时工,明白吧。

所以意思就是如果你要返回值,你就用 AsyncResult 对象来包一下,这个 AsyncResult 就是 temporary worker。

就像这样:


image.png


接着我们把目光放到注解的 value 属性上:


image.png


这个注解,看注释上面的意思,就是说这个应该填一个线程池的 bean 名称,相当于指定线程池的意思。

也不知道理解的对不对,等会写个方法验证一下就知道了。

好了,到现在,我把信息整理汇总一下。

  • 我之前完全不懂这个注解,现在我有一个 Demo 了,搭建 Demo 的时候我发现除了 @Async 注解之外,还需要加上 @EnableAsync 注解,比如加在启动类上。
  • 然后把这个默认的线程池当做黑盒测试了一把,我怀疑它的核心线程数默认是 8,队列长度无线长。有内存溢出的风险。
  • 通过阅读 @Async 上的注解,我发现返回值只能是 void 或者 Future 类型,否则即使返回了其他值,不会报错,但是返回的值是 null,有空指针风险。
  • @Async 注解中有一个 value 属性,看注释应该是可以指定自定义线程池的。

接下来我把要去探索的问题排个序,只聚焦到 @Async 的相关问题上:

  • 1.默认线程池的具体配置是什么?
  • 2.源码是怎么做到只支持 void 和 Future 的?
  • 3.value 属性是干什么用的?
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
5月前
|
Java 编译器 Spring
一文搞懂:什么是注解?
一文搞懂:什么是注解?
35 0
|
6月前
|
Java
SpringAop切面编程(一看就会用)
SpringAop切面编程(一看就会用)
24 0
|
6月前
|
监控 安全 Java
Spring注解之恋:@Async和@Transactional的双重奏
Spring注解之恋:@Async和@Transactional的双重奏
863 0
|
安全 Java 大数据
一文搞懂什么是“注解”
一文搞懂什么是“注解”
301 0
一文搞懂什么是“注解”
|
JSON 数据格式
@JsonProperty与@JSONField注解用处
@JsonProperty与@JSONField注解用处
|
Java Spring 容器
|
JSON 前端开发 Java
|
Java 测试技术 Spring
@Aysnc注解其实也就这么回事! (下)
@Aysnc注解其实也就这么回事! (下)
425 1
@Aysnc注解其实也就这么回事! (下)
|
Java 中间件 程序员
@Aysnc注解其实也就这么回事! (中)
@Aysnc注解其实也就这么回事! (中)
283 0
@Aysnc注解其实也就这么回事! (中)
|
Java 程序员 编译器
神奇的注解是怎么实现的,你不好奇吗?
最近学完 java的ssm框架之后,我有了好多的领悟,许多非常值得学习的思想,其中还有一个让我疑惑的就是框架中推荐的注解式开发,添加一条注解就可以自动注入,哇哇哇……此处省略一万字,它好神奇啊,我想看看它的全貌,点进源码,我蒙了,很短,并不是我想的一堆逻辑处理,于是就查资料学习,总结了以下知识点,一起来学习吧!芜湖,拿下它 🚀
151 0
神奇的注解是怎么实现的,你不好奇吗?