Java Springboot 多线程 异步调用 全解析

本文主要介绍Springboot异步调用,包括:

1、Springboot同步调用演示;

2、Springboot两种异步调用方式;

3、Springboot使用注解的异步调用方式详解;

4、Springboot使用注解异步调用时的异常处理;

5、利用Future获取异步子线程的执行结果;

6、Springboot使用注解异步调用与事务一起使用时的注意事项;

7、Springboot异步调用时多线程池实现;

8、Springboot使用注解异步调用失效的原因汇总;

Java Springboot 多线程 异步调用 全解析

图0

1、Springboot同步调用演示,如下面图1所示:

Java Springboot 多线程 异步调用 全解析

图1

我们在thread2方法中打印出调用线程名和调用前后时间以及间隔时间。

结果如下面图2所示:

Java Springboot 多线程 异步调用 全解析

图2

程序3秒后返回并打印出的结果和我们的预期一样。

2、Springboot两种异步调用方式

异步调用的效果就是主线程可以提前返回,然后交由任务子线程进行处理。

下面会介绍Springboot异步调用的两种方式,并着重介绍最常用的第二种方式。

<1、自定义线程类;

<2、使用@Async和@EnableAsync注解;

<1、自定义线程类

Controller类代码如下面图3所示:

Java Springboot 多线程 异步调用 全解析

图3

在Controller类的thread3方法中打印出调用线程名和调用前后时间,并且创建了一个新线程。(创建线程也可以使用线程池的方式)。

如下面图4所示:

Java Springboot 多线程 异步调用 全解析

图4

在ThreadTask类的run方法中打印出调用线程名和调用前后时间以及间隔时间,注意run方法中的打印语句前都打印了“---”。

结果如下面图5所示:

Java Springboot 多线程 异步调用 全解析

图5

从结果中可以看出,Controller类的thread3方法执行时间间隔为0秒,而ThreadTask类的run方法执行时间间隔为3秒。和我们预期的相符。

3、Springboot使用注解的异步调用方式详解

3.1、使用@Async和@EnableAsync注解

3.1.1、首先使用@EnableAsync注解开启异步调用功能

该注解可以放置的位置有:

<1、启动类上方,如下面图6所示:

Java Springboot 多线程 异步调用 全解析

图6

<2、调用异步的当前类上方,如下面图7所示:

Java Springboot 多线程 异步调用 全解析

图7

<3、在配置类上方使用,如下面图8所示:

Java Springboot 多线程 异步调用 全解析

图8

配置类中,如果不指定 Executor ,则默认使用 SimpleAsyncTaskExecutor。

我们以第<3种为例进行介绍,当然你也可以在启动类中进行相关配置。

如下面图9所示:

Java Springboot 多线程 异步调用 全解析

图9

新建TestAsyncService类,并在该类中新建serviceThread4方法,在serviceThread4方法上加@Async注解,注意我这里为了方便没有在ServiceImpl中写代码逻辑。

如下面图10、图11所示:

Java Springboot 多线程 异步调用 全解析

图10

Java Springboot 多线程 异步调用 全解析

图11

将TestAsyncService类注入到TestAsyncController类,并新建thread4方法,其中是调用taService.serviceThread4();异步方法。

如下面图12所示:

Java Springboot 多线程 异步调用 全解析

图12

从结果可以看到,主线程http-nio-8085-exec-4时间间隔为0,直接返回,而子线程asyncExecutor-1为3秒。

注意:加了@Async注解的异步方法,不能在本类中使用this进行调用,因为Spring是通过创建代理来进行异步调用的,故在本类调用是无效的。(如果不怕麻烦,也是有办法的。)

4、Springboot使用注解异步调用时的异常处理

如下面图13所示:

Java Springboot 多线程 异步调用 全解析

图13

在SpringAsyncConfigurer类增加getAsyncUncaughtExceptionHandler()方法。

修改TestAsyncService类的serviceThread4方法,如下图红框中所示:

Java Springboot 多线程 异步调用 全解析

图14

此方法将会抛出空指针异常。

如下面图15所示:

Java Springboot 多线程 异步调用 全解析

图15

5、利用Future获取异步子线程的执行结果

如下面图16所示:

Java Springboot 多线程 异步调用 全解析

图16

在TestAsyncService类中增加serviceThread5方法,如果正常出现结果,则返回success:参数值,出现异常则返回error

如下面图17所示:

Java Springboot 多线程 异步调用 全解析

图17

Controller类中增加thread5方法,注意方法中获取子线程返回值的地方,其中,future.get()是具有阻塞特性的,也就是说,虽然子线程是以异步的方式执行,但如果我们在Controller类的thread5方法中调用future.get(),则thread5方法同样会阻塞直到子线程执行完毕。

如下面图18所示:

Java Springboot 多线程 异步调用 全解析

图18

从结果中也可以发现,因为future.get()的阻塞特性,实际上thread5方法的执行时间间隔为4秒,也就是子线程的睡眠时间。

6、Springboot中异步调用事务的处理机制

被@Async注解的方法,也可以同时使用@Transactional注解;在调用数据库操作之时,将无法产生事务管理的控制,原因就在于其是基于异步处理的操作。

那该如何给这些操作添加事务管理呢?我们可以将需要事务管理操作的方法放置到异步方法内部,在内部被调用的方法上添加@Transactional.

例如:方法1,使用了@Async/@Transactional来标注,但是无法产生事务控制的目的。方法2,使用了@Async来标注,方法2中调用了方法3、方法4,方法3/方法4分别使用@Transactional做了标注,则可以实现事务控制的目的。

7、Springboot异步调用时多线程池实现,如下面图19所示:

Java Springboot 多线程 异步调用 全解析

图19

在SpringAsyncConfigurer类中像上图中这样创建bean即可。使用时如下面图20在@Async注解中加入属性值即可。

Java Springboot 多线程 异步调用 全解析

图20

8、Springboot异步调用失效原因汇总

<1、异步方法和调用方法在同一个类中;

<2、异步方法使用static修饰;

<3、注解扫描时,要注意过滤,避免重复实例化,因为会有覆盖问题;

注:<1和<2是一类原因,都是因为Spring通过代理类来使注解生效的,而Spring代理一般有动态代理和使用cglib进行生成,且为原来类或者接口的子类故不能被子类正常重写的方法,即使是加了@Async注解的方法也是不能生效的,像事务注解或其他自定义注解等不能生效,都可能有代理类无法正常使用的原因。(具体原因和使用动态代理和使用cglib的差别后面会有更细致的梳理,在这里只是我能想起的知识点)。

本篇完。

持续更新完善中......欢迎关注,共同学习


分享到:


相關文章: