同步代码写起来简单,但就是怕遇到耗时操做,会影响效率和吞吐量。
此时异步代码才是王者,但涉及多线程和线程池,以及异步结果的获取,写起来颇为麻烦。
不过在遇到SpringBoot异步任务时,这个问题就不存在了。由于Spring家族是最替用户考虑的。
结果就是,像同步同样简单,像异步同样强大。
众所熟悉的同步代码
先准备一些代码,为了模拟耗时操做,在其中加入线程睡眠语句。
同时打印出运行这些代码的线程信息。以下图01:
html
其中一个是没有返回值的,一个是有返回值的。
而后把它注入到另外一个类里进行调用,在调用时也输出一下主线程信息。以下图02:
web
下面是输出结果,以下图03:
面试
能够看到这些代码运行在主线程中,因此这些代码的耗时操做会影响主线程。
首选的方案就是把耗时操做放入另外一个线程中执行(一般称为工做线程),把主线程解放出来。
同步代码的异步化改造
因为SpringBoot已经帮咱们作好了一切,只需按要求改造便可,只需两步,真的是很是简单。
第一步,引入启用异步任务的注解,@EnableAsync,以下图04:
spring
第二步,在原来的方法上标上@Async注解,以下图05:
安全
这就行了,而后像普通方法同样调用,以下图06:
springboot
看下输出结果,以下图07:
服务器
能够看到主线程的id是1,并且瞬间执行完。任务在另外一个线程id为17的线程中执行,且等耗时操做执行完后才结束。
代码彻底不变,只需加两个注解,同步立马变成异步啦。简直爽歪歪了。
主要是由于这个方法没有返回值,若是有的话,只需改下返回类型便可。
SpringBoot一共支持三种返回类型,来逐一看下。
第一种,返回类型为Java的Future<?>,以下图08:
多线程
熟悉Java多线程的朋友对这个类都应该不陌生。为了代码能正常编译,在方法最后须要return一个这样的类型。
在同步代码中,咱们原来return的是一个Object类型,显然不知足需求,因此SpringBoot就想了一个办法。
新增了一个类,AsyncResult,使用它进行类型适配,这也是此类的主要做用,保证编译经过。
这个类就像一个“类型”占位符同样,若是你真正了解Java多线程的话就会明白,不然绝对不明白。
而后就像普通方法调用同样调用它,接着经过while循环等待异步任务完成后,输出返回结果。
注意,我特地输出了一下方法调用返回的future变量,以下图09:
架构
输出结果以下图10:
app
能够看到任务是在线程id为17的线程中执行,主线程不断睡眠等待,直到任务完成后才获取到任务的返回结果。
重要时刻来临,能够看到咱们输出的future变量类型是Java的FutureTask类,而咱们实际在代码中return的是Spring的AsyncResult类。
是否是很奇怪呢?其实一点都不怪,这和Java多线程有关,若是还不明白的话,后面有说明。
第二种,返回类型为Spring的ListenableFuture<?>,以下图11:
能够看到代码在return的时候写法是同样的,那这个类型的好处是什么呢?答案是能够注册回调。
有了回调,任务在完成后会自动执行回调代码,因此主线程就不用等了。
所以在调用时要注册回调代码,包括成功回调和失败回调,以下图12:
注意,咱们一样打印一下方法返回变量listenableFuture的类型。
输出结果以下图13:
能够看到此时主线程瞬间执行完毕。任务在线程id为17的线程中执行,完成后执行了回调,且在同一个线程中。
一样变量listenableFuture的类型是Spring的ListenableFutureTask类,并非咱们在代码里return的AsyncResult类。
第三种,返回类型为Java的CompletableFuture<?>,以下图14:
这个类型是Java 8新增的,能够对异步任务进行特殊的操做。
而后进行调用,一样输出下返回变量类型,以下图15:
输出结果以下图16:
输出内容很容易看懂。重点看下返回变量的类型,它就是Java的CompletableFuture<?>类。
那咱们在代码中return的是什么类型呢?以下图17:
能够看到和真实调用时返回的仍是不同。若是还不明白,下面来讲明下。
Spring在遇到标有@Async的方法时会生成代理,代理作的事情就是把该方法包装成一个任务submit到线程池中。
在submit的时候会返回真正的返回值,就是上面咱们在调用方法时输出的。
而咱们在写@Async方法代码时return的是一个相似类型占位符的类,它的一个做用就是保证编译经过。
另外一个做用就是传递返回值,在任务执行完成时,把值往外层传递。
线程池的个性化按需配置
对于Java来讲,几乎全部的异步执行代码都是提交到线程池中来执行的,由于线程池能够管理好线程,咱们就不用操心了。
不过咱们依然能够对线程池进行配置,如核心线程数、最大线程数、内部队列长度等等。
SpringBoot固然也支持这些配置,按照惯例,这些配置也是放在application.yml配置文件中的。
一些IDE是能够进行自动提示的,以下图18:
这些配置的前缀是spring.task.execution,主要包括三类配置,线程池中线程的数目和队列的大小,线程池关闭时的行为,线程名称的前缀。
有求知欲的朋友可能会寻思,这些配置到底是如何生效的呢?下面就来知足一下好奇心,其实很简单。
SpringBoot的特性之一就是自动配置,这些自动配置代码都位于这个jar包中,以下图19:
这个jar包名称很容易记住,因此最好都能记住,下次有疑问本身就能够去找了。
咱们在这个jar包里寻找和任务(task)相关的包名称,以下图20:
前两个类是和任务执行相关的,其中以Properties结尾的类是用于存放application.yml里面的配置的。
以AutoConfiguration结尾的类是用于自动配置的,主要是bean定义的注册。
这种写法是SpringBoot自动配置的标准模式,能够看看其它的,都是这样的。
看下TaskExecutionProperties类,以下图21:
指定好前缀后,配置文件中的配置项和类中的属性彻底是一一对应的,并且类中属性能够有默认值,这样配置文件中没有配置时就使用默认值。
再来看下TaskExecutionAutoConfiguration类,这里面就注册了两个bean,以下图22:
首先使用刚刚的属性注册一个TaskExecutorBuilder类型的bean,而后再使用它注册一个ThreadPoolTaskExecutor类型的bean。
其实异步任务执行主要是要找到一个线程池的bean,来完成任务的提交,具体寻找逻辑的以下:
1)若是容器中存在惟一一个TaskExecutor类型的bean,那就用它。不然继续往下。
2)若是容器中存在一个名称为taskExecutor且类型为Executor的bean,就用它,不然继续往下。
3)将使用SimpleAsyncTaskExecutor类进行异步方法调用。
void异步方法的异常处理
须要注意的是,返回类型为void的异步方法,将不会向调用者传递异常。默认状况下,这些未捕获的异常仅仅输出一下日志。
因此对于void方法必定要本身处理好异常。若是恰巧没处理好,怎么办呢?不要着急。
SpringBoot提供了统一的未捕获异常处理方式,只要实现一个接口便可,以下图23:
咱们能够获取到抛出的异常,还有抛出异常时执行的异步方法,还有调用该异步方法时传入的参数。
那么,对于有返回值的异步方法,则自己能够传递异常,因此不会使用这种方式。这一点需注意。
做者寄语
异步方法的原理很简单,就是在单独的线程中执行一个方法或代码片断。
不过有两方面须要注意,技术方面和业务方面:
技术方面:
1)如何获取异步方法的返回值
2)如何处理异步方法产生的异常
3)如何处理异步方法超时的问题
业务方面:
1)异步方法执行成功时对业务的影响
2)异步方法抛出异常时对业务的影响
3)异步方法执行超时时对业务的影响
(END)
>>> 玩转SpringBoot系列文章 <<<
【玩转SpringBoot】用好条件相关注解,开启自动配置之门
【玩转SpringBoot】看似复杂的Environment其实很简单
【玩转SpringBoot】让错误处理从新由web服务器接管
【玩转SpringBoot】SpringBoot应用的启动过程一览表
【玩转SpringBoot】经过事件机制参与SpringBoot应用的启动过程
>>> 品Spring系列文章 <<<
品Spring:SpringBoot和Spring到底有没有本质的不一样?
品Spring:SpringBoot轻松取胜bean定义注册的“第一阶段”
品Spring:SpringBoot发起bean定义注册的“二次攻坚战”
品Spring:注解之王@Configuration和它的一众“小弟们”
品Spring:对@PostConstruct和@PreDestroy注解的处理方法
品Spring:对@Autowired和@Value注解的处理方法
品Spring:真没想到,三十步才能完成一个bean实例的建立
品Spring:关于@Scheduled定时任务的思考与探索,结果尴尬了
>>> 热门文章集锦 <<<
爸爸又给Spring MVC生了个弟弟叫Spring WebFlux
【面试】吃透了这些Redis知识点,面试官必定以为你很NB(干货 | 建议珍藏)
【面试】若是你这样回答“什么是线程安全”,面试官都会对你另眼相看(建议珍藏)
【面试】迄今为止把同步/异步/阻塞/非阻塞/BIO/NIO/AIO讲的这么清楚的好文章(快快珍藏)
【面试】一篇文章帮你完全搞清楚“I/O多路复用”和“异步I/O”的前世此生(深度好文,建议珍藏)
做者是工做超过10年的码农,如今任架构师。喜欢研究技术,崇尚简单快乐。追求以通俗易懂的语言解说技术,但愿全部的读者都能看懂并记住。
原文出处:https://www.cnblogs.com/lixinjie/p/playing-springboot-009.html