首页>>后端>>SpringBoot->springboot异步调用?

springboot异步调用?

时间:2023-12-01 本站 点击:0

SpringBoot之@Async异步调用

利用 Spring Initializer 创建一个 gradle 项目 spring-boot-async-task,创建时添加相关依赖。

在 Spring Boot 入口类上配置 @EnableAsync 注解开启异步处理。

创建任务抽象类 AbstractTask,并分别配置三个任务方法 doTaskOne(),doTaskTwo(),doTaskThree()。

下面通过一个简单示例来直观的理解什么是同步调用:

定义 Task 类,继承 AbstractTask,三个处理函数分别模拟三个执行任务的操作,操作消耗时间随机取(10 秒内)。

在 单元测试 用例中,注入 Task 对象,并在测试用例中执行 doTaskOne(),doTaskTwo(),doTaskThree() 三个方法。

执行单元测试,可以看到类似如下输出:

任务一、任务二、任务三顺序的执行完了,换言之 doTaskOne(),doTaskTwo(),doTaskThree() 三个方法顺序的执行完成。

上述的可以看到 执行时间比较长,若这三个任务本身之间 不存在依赖关系,可以 并发执行 的话,同步调用在 执行效率 方面就比较差,可以考虑通过 异步调用 的方式来 并发执行。

创建 AsyncTask类,分别在方法上配置 @Async 注解,将原来的 同步方法 变为 异步方法。

在 单元测试 用例中,注入 AsyncTask 对象,并在测试用例中执行 doTaskOne(),doTaskTwo(),doTaskThree() 三个方法。

执行单元测试,可以看到类似如下输出:

如果反复执行单元测试,可能会遇到各种不同的结果,比如:

原因是目前 doTaskOne(),doTaskTwo(),doTaskThree() 这三个方法已经 异步执行 了。主程序在 异步调用 之后,主程序并不会理会这三个函数是否执行完成了,由于没有其他需要执行的内容,所以程序就 自动结束 了,导致了 不完整 或是 没有输出任务 相关内容的情况。

根据业务需求,可以将暂时不需要立即获得处理的方法设置为 @Async .

比如用户在前端点击完成了登录操作,这时候根据业务要求需要在登录成功之后进行埋点的处理.

其实埋点成功与否都不影响用户操作,这时候就可以将埋点方法设置为@Async.

个人认为此类任务通常有三个特征:

为了让 doTaskOne(),doTaskTwo(),doTaskThree() 能正常结束,假设我们需要统计一下三个任务 并发执行 共耗时多少,这就需要等到上述三个函数都完成动用之后记录时间,并计算结果。

那么我们如何判断上述三个 异步调用 是否已经执行完成呢?我们需要使用 FutureT 来返回 异步调用 的 结果。

在 单元测试 用例中,注入 AsyncCallBackTask 对象,并在测试用例中执行 doTaskOneCallback(),doTaskTwoCallback(),doTaskThreeCallback() 三个方法。循环调用 Future 的 isDone() 方法等待三个 并发任务 执行完成,记录最终执行时间。

在测试用例一开始记录开始时间;在调用三个异步函数的时候,返回Future类型的结果对象;在调用完三个异步函数之后,开启一个循环,根据返回的Future对象来判断三个异步函数是否都结束了。若都结束,就结束循环;若没有都结束,就等1秒后再判断。跳出循环之后,根据结束时间 - 开始时间,计算出三个任务并发执行的总耗时。

执行一下上述的单元测试,可以看到如下结果:

可以看到,通过 异步调用,让任务一、任务二、任务三 并发执行,有效的 减少 了程序的 运行总时间。

在上述操作中,创建一个 线程池配置类 TaskConfiguration ,并配置一个 任务线程池对象 taskExecutor。

上面我们通过使用 ThreadPoolTaskExecutor 创建了一个 线程池,同时设置了以下这些参数:

创建 AsyncExecutorTask类,三个任务的配置和 AsyncTask 一样,不同的是 @Async 注解需要指定前面配置的 线程池的名称 taskExecutor。

在 单元测试 用例中,注入 AsyncExecutorTask 对象,并在测试用例中执行 doTaskOne(),doTaskTwo(),doTaskThree() 三个方法。

执行一下上述的 单元测试,可以看到如下结果:

执行上面的单元测试,观察到 任务线程池 的 线程池名的前缀 被打印,说明 线程池 成功执行 异步任务!

解决方案如下,重新设置线程池配置对象,新增线程池 setWaitForTasksToCompleteOnShutdown() 和 setAwaitTerminationSeconds() 配置:

Spring Boot - 异步任务

有时候,前端可能提交了一个耗时任务,如果后端接收到请求后,直接执行该耗时任务,那么前端需要等待很久一段时间才能接受到响应。如果该耗时任务是通过浏览器直接进行请求,那么浏览器页面会一直处于转圈等待状态。一个简单的例子如下所示:

当我们在浏览器请求 localhost:8080/async/ 页面时,可以看到浏览器一直处于转圈等待状态,这样体验十分不友好。

事实上,当后端要处理一个耗时任务时,通常都会将耗时任务提交到一个异步任务中进行执行,此时前端提交耗时任务后,就可直接返回,进行其他操作。

在 Java 中,开启异步任务最常用的方式就是开辟线程执行异步任务,如下所示:

这时浏览器请求 localhost:8080/async/ ,就可以很快得到响应,并且耗时任务会在后台得到执行。

一般来说,前端不会关注耗时任务结果,因此前端只需负责提交该任务给到后端即可。但是如果前端需要获取耗时任务结果,则可通过 Future 等方式将结果返回,详细内容请参考后文。

事实上,在 Spring Boot 中,我们不需要手动创建线程异步执行耗时任务,因为 Spring 框架已提供了相关异步任务执行解决方案,本文主要介绍下在 Spring Boot 中执行异步任务的相关内容。

Spring 3.0 时提供了一个 @Async 注解,该注解用于标记要进行异步执行的方法,当在其他线程调用被 @Async 注解的方法时,就会开启一个线程执行该方法。

注 : @Async 注解通常用在方法上,但是也可以用作类型上,当类被 @Async 注解时,表示该类中所有的方法都是异步执行的。

在 Spring Boot 中,如果要执行一个异步任务,只需进行如下两步操作:

被 @Async 注解的异步任务方法存在相关限制:

默认情况下,Spring 会自动搜索相关线程池定义:要么是一个唯一 TaskExecutor Bean 实例,要么是一个名称为 taskExecutor 的 Executor Bean 实例。如果这两个 Bean 实例都不存在,就会使用 SimpleAsyncTaskExecutor 来异步执行被 @Async 注解的方法。

综上,可以知道,默认情况下,Spring 使用的 Executor 是 SimpleAsyncTaskExecutor , SimpleAsyncTaskExecutor 每次调用都会创建一个新的线程,不会重用之前的线程。很多时候,这种实现方式不符合我们的业务场景,因此通常我们都会自定义一个 Executor 来替换 SimpleAsyncTaskExecutor 。

对于自定义 Executor(自定义线程池),可以分为如下两个层级:

前文介绍过,对于被 @Async 注解的异步方法,只能返回 void 或者 Future 类型。对于返回 Future 类型数据,如果异步任务方法抛出异常,则很容易进行处理,因为 Future.get() 会重新抛出该异常,我们只需对其进行捕获即可。但是对于返回 void 的异步任务方法,异常不会传播到被调用者线程,因此我们需要自定义一个额外的异步任务异常处理器,捕获异步任务方法抛出的异常。

自定义异步任务异常处理器的步骤如下所示:

Spring Boot 异步任务 -- @EnableAsync 详解

@EnableAsync 注解启用了 Spring 异步方法执行功能,在 Spring Framework API 中有详细介绍。

@EnableAsync 默认启动流程:

1 搜索关联的线程池定义:上下文中唯一的 TaskExecutor 实例,或一个名为 taskExecutor 的 java.util.concurrent.Executor 实例;

2 如果以上都没找到,则会使用 SimpleAsyncTaskExecutor 处理异步方法调用。

注意:具有 void 返回类型的带注释方法不能将任何异常发送回调用者,默认情况下此类未捕获异常只会被记录日志。

定制 @EnableAsync 启动行为:

1 实现 AsyncConfigurer 接口

2 实现 getAsyncExecutor() 方法自定义 java.util.concurrent.Executor

3 实现 getAsyncUncaughtExceptionHandler() 方法自定义 AsyncUncaughtExceptionHandler

示例:修改 AsyncConfig 配置类实现

Spring Boot 微服务异步调用 @EnableAsync @Async

第一步:在Application启动类上面加上@EnableAsync注解

第二步:定义[线程池]

第三步:在异步方法上添加@Async

第四步:测试

输出结果:

时间testA:2

开始testB

开始testA

完成testA

完成testB

任务testA,当前线程:async-thread-pool-1

时间testB:3002

异步方法@Async注解失效情况:

(1)在@SpringBootApplication启动类没有添加注解@EnableAsync

(2)调用方法和异步方法写在同一个类,需要在不同的类才能有效。

(2)调用的是静态(static )方法

(3)调用(private)私有化方法

个别失效报错情况:

报错一:提示需要在@EnableAsync上设置proxyTargetClass=true来强制使用基于cglib的代理。注解上加上即可。

Springboot 使用@Async开启异步调用

大家都知道,java是同步顺序执行。当需要异步执行时,需要新创建一个线程完成。

1. 使用常规的方法显示异步调用

第一步 新建 ThreadTest.java 实现 Runnable 接口

第二步 新建测试执行

当然,除了这种显式 new Thread 对象,我们通过线程池获取线程名称,这里不做演示。我们熟悉的spring 在 spring3中提供了@Async注解,来方便开发者优雅的使用异步调用。

2.使用 springboot @Async注解,优雅的实现异步调用

第一步 开启 异步调用注解。

第二步 定义线程池

第三步 创建service 测试类 TestService.java

第四步 新建 Service 实现类 ,TestServiceImpl.java

第五步 测试执行 ,执行结果

SpringBoot使用@Async优雅的异步调用就暂时记录到这里,欢迎评论区一起讨论学习。

一图看懂Spring Boot 异步框架

在SpringBoot的日常开发中,一般都是同步调用的。但经常有特殊业务需要做异步来处理,例如:注册新用户,送100个积分,或下单成功,发送push消息等等。

就拿注册新用户为什么要异步处理?

在SpringBoot中使用异步调用是很简单的,只需要使用@Async注解即可实现方法的异步调用。

采用@EnableAsync来开启异步任务支持,另外需要加入@Configuration来把当前类加入springIOC容器中。

增加一个service类,用来做积分处理。

@Async添加在方法上,代表该方法为异步处理。

@Async注解,在默认情况下用的是SimpleAsyncTaskExecutor线程池,该线程池不是真正意义上的线程池,因为线程不重用,每次调用都会新建一条线程。

可以通过控制台日志输出查看,每次打印的线程名都是[task-1]、[task-2]、[task-3]、[task-4].....递增的。

@Async注解异步框架提供多种线程

SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。

SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方。

ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类。

ThreadPoolTaskScheduler:可以使用cron表达式。

ThreadPoolTaskExecutor :最常使用,推荐。 其实质是对java.util.concurrent.ThreadPoolExecutor的包装。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/SpringBoot/6057.html