springboot(10)异步任务
文章目录
- 1、SpringBoot异步任务
- 1.1使用注解EnableAsync开启异步任务支持
- 1.2使用`@Async`注解标记要进行异步执行的方法
- 1.3controller测试
- 2.异步任务相关限制
- 3.1自定义 Executor
- 3.1.1应用层级:
- 3.1.2方法层级:
- 3.2自定义 Executor (第二种方式)
- 4.1异常处理
- 4.1.1自定义一个异常处理器类实现接口如下所示:
- 4.1.2创建一个自定义`Executor`异步配置类,将我们的自定义异常处理器设置到其接口上
- 4.1.3测试
- 4.2异常处理(第二种方式)
在SpringBoot应用程序中,有时需要执行一些长时间运行的操作,如发送电子邮件或从外部API获取数据。 这些操作可能需要几秒钟或几分钟才能完成。 如果您在主线程上执行此类操作,则应用程序停止响应,可能会导致用户体验不佳。
为了避免这种情况,并使应用程序在执行此类操作时继续响应,我们可以使用SpringBoot的异步任务功能。 异步任务是指可以在后台线程上执行的任务,因此不会阻塞主线程。
有时候,前端可能提交了一个耗时任务,如果后端接收到请求后,直接执行该耗时任务,那么前端需要等待很久一段时间才能接受到响应。如果该耗时任务是通过浏览器直接进行请求,那么浏览器页面会一直处于转圈等待状态。
事实上,当后端要处理一个耗时任务时,通常都会将耗时任务提交到一个异步任务中进行执行,此时前端提交耗时任务后,就可直接返回,进行其他操作。
1、SpringBoot异步任务
SpringBoot开启异步任务的步骤如下:
- 在SpringBootApplication类上添加@EnableAsync注解,启用SpringBoot异步任务支持。
- 在异步方法所在的类上添加@Async注解,标记该类中需要异步执行的方法。
- 在异步方法中使用CompletableFuture等类处理异步操作。
使用SpringBoot异步任务功能可以轻松地将长时间运行的操作转换为异步任务,提高应用程序的响应性能和用户体验。
1.1使用注解EnableAsync开启异步任务支持
@SpringBootApplication
@EnableAsync
public class ApplicationStarter {public static void main(String[] args) {SpringApplication.run(ApplicationStarter.class,args);}
}
1.2使用@Async
注解标记要进行异步执行的方法
@Service
public class AsyncService {@Asyncpublic void t1() throws InterruptedException {// 模拟耗时任务Thread.sleep(TimeUnit.SECONDS.toMillis(5));}@Asyncpublic Future<String> t2() throws InterruptedException {// 模拟耗时任务Thread.sleep(TimeUnit.SECONDS.toMillis(5));return new AsyncResult<>("async tasks done!");}
}
1.3controller测试
@Autowiredprivate AsyncService asyncService;@GetMapping("/task1")public String asyncTaskWithoutReturnType() throws InterruptedException {asyncService.t1();return "rrrr";}@GetMapping("/task2")public String asyncTaskWithReturnType() throws InterruptedException, ExecutionException {asyncService.t2();return "aaaaaaa";}
被@Async
注解的方法可以接受任意类型参数,但只能返回void
或Future
类型数据
所以当异步方法返回数据时,需要使用Future
包装异步任务结果,上述代码使用AsyncResult包装异步任务结果,AsyncResult
间接继承Future
,是 Spring 提供的一个可用于追踪异步方法执行结果的包装类。其他常用的Future
类型还有 Spring 4.2 提供的ListenableFuture,或者 JDK 8 提供的CompletableFuture,这些类型可提供更丰富的异步任务操作。
如果前端需要获取耗时任务结果,则异步任务方法应当返回一个Future
类型数据,此时Controller
相关接口需要调用该Future
的get()
方法获取异步任务结果,get()
方法是一个阻塞方法,因此该操作相当于将异步任务转换为同步任务,浏览器同样会面临我们前面所讲的转圈等待过程,但是异步执行还是有他的好处的,因为我们可以控制get()
方法的调用时序,因此可以先执行其他一些操作后,最后再调用get()
方法。
2.异步任务相关限制
被@Async
注解的异步任务方法存在相关限制:
- 被
@Async
注解的方法必须是public
的,这样方法才可以被代理。 - 不能在同一个类中调用
@Async
方法,因为同一个类中调用会绕过方法代理,调用的是实际的方法。 - 被
@Async
注解的方法不能是static
。 @Async
注解不能与 Bean 对象的生命周期回调函数(比如@PostConstruct
)一起注解到同一个方法中。- 异步类必须注入到 Spring IOC 容器中(也即异步类必须被
@Component
/@Service
等进行注解)。 - 其他类中使用异步类对象必须通过
@Autowired
等方式进行注入,不能手动new
对象。
SpringBoot异步任务功能虽然可以提高应用程序的响应性能,但还是有一些限制需要注意:
- 异步方法不能与同步方法在同一个类中定义。必须将异步方法定义在单独的类中,或者使用CGLIB代理来将异步方法包装在另一个类中。
- 异步方法不能是private或final的。
- 异步方法不能返回void类型。必须返回一个Future或CompletableFuture对象,以便在异步任务完成后处理结果。
- 如果异步方法在同一类中调用另一个异步方法,则调用将不会异步执行,而是在同一线程中同步执行。
总之,虽然SpringBoot的异步任务功能可以提高应用程序的响应性能,但在使用时需要注意这些限制,以确保异步方法能够正确地执行并保持应用程序的稳定性。
3.1自定义 Executor
默认情况下,Spring 会自动搜索相关线程池定义:要么是一个唯一TaskExecutor Bean 实例,要么是一个名称为taskExecutor
的Executor Bean 实例。如果这两个 Bean 实例都不存在,就会使用SimpleAsyncTaskExecutor来异步执行被@Async
注解的方法。
综上,可以知道,默认情况下,Spring 使用的 Executor 是SimpleAsyncTaskExecutor
,SimpleAsyncTaskExecutor
每次调用都会创建一个新的线程,不会重用之前的线程。很多时候,这种实现方式不符合我们的业务场景,因此通常我们都会自定义一个 Executor 来替换SimpleAsyncTaskExecutor
。
对于自定义 Executor(自定义线程池),可以分为如下两个层级:
- 应用层级:即全局生效的 Executor。依据 Spring 默认搜索机制,其实就是配置一个全局唯一的
TaskExecutor
实例或者一个名称为taskExecutor
的Executor
实例即可,如下所示: - 方法层级:即为单独一个或多个方法指定运行线程池,其他未指定的异步方法运行在默认线程池。如下所示:
3.1.1应用层级:
下面代码定义了一个名称为taskExecutor
的Executor
,此时@Async
方法默认就会运行在该Executor
中。
@Configuration
public class ExcuterConfig {@Bean("taskExecutor")public Executor getAsyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 设置核心线程数int cores = Runtime.getRuntime().availableProcessors();executor.setCorePoolSize(cores);// 设置最大线程数executor.setMaxPoolSize(20);// 等待所有任务结束后再关闭线程池executor.setWaitForTasksToCompleteOnShutdown(true);// 设置线程默认前缀名executor.setThreadNamePrefix("Application-Level-Async-");return executor;}
}
3.1.2方法层级:
package com.buba.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;@Configuration
public class ExcuterConfig {@Bean("methodLevelExecutor1")public TaskExecutor getAsyncExecutor1() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 设置核心线程数executor.setCorePoolSize(4);// 设置最大线程数executor.setMaxPoolSize(20);// 等待所有任务结束后再关闭线程池executor.setWaitForTasksToCompleteOnShutdown(true);// 设置线程默认前缀名executor.setThreadNamePrefix("Method-Level-Async1-");return executor;}@Bean("methodLevelExecutor2")public TaskExecutor getAsyncExecutor2() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 设置核心线程数executor.setCorePoolSize(8);// 设置最大线程数executor.setMaxPoolSize(20);// 等待所有任务结束后再关闭线程池executor.setWaitForTasksToCompleteOnShutdown(true);// 设置线程默认前缀名executor.setThreadNamePrefix("Method-Level-Async2-");return executor;}
}
上述特意设置了多个TaskExecutor
,因为如果只设置一个TaskExecutor
,那么 Spring 就会默认采用该TaskExecutor
作为所有@Async
的Executor
,而设置了多个TaskExecutor
,Spring 检测到全局存在多个Executor
,就会降级使用默认的SimpleAsyncTaskExecutor
,此时我们就可以为@Async
方法配置执行线程池,其他未配置的@Async
就会默认运行在SimpleAsyncTaskExecutor
中,这就是方法层级的自定义 Executor。如下代码所示:
@Service
public class AsyncService {@Async("methodLevelExecutor1")public void t1() throws InterruptedException {// 模拟耗时任务Thread.sleep(TimeUnit.SECONDS.toMillis(5));}@Async("methodLevelExecutor2")public Future<String> t2() throws InterruptedException {// 模拟耗时任务Thread.sleep(TimeUnit.SECONDS.toMillis(5));return new AsyncResult<>("async tasks done!");}
}
3.2自定义 Executor (第二种方式)
Spring Boot提供了默认的任务执行器,但是有时候我们需要自定义任务执行器以更好地控制任务执行的线程池。在Spring Boot中,我们可以通过实现AsyncConfigurer接口来自定义任务执行器。下面是一个使用自定义任务执行器的示例:
@Configuration
@EnableAsync
public class AppConfig implements AsyncConfigurer {@Overridepublic Executor getAsyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(10);executor.setMaxPoolSize(100);executor.setQueueCapacity(500);executor.setThreadNamePrefix("MyExecutor-");executor.initialize();return executor;}
}
在上面的示例中,我们实现了AsyncConfigurer接口,并覆盖了getAsyncExecutor()方法。在该方法中,我们创建了一个ThreadPoolTaskExecutor对象,并设置了线程池的核心线程数、最大线程数、队列容量和线程名称前缀。最后,我们返回这个线程池对象。
通过实现AsyncConfigurer接口并覆盖getAsyncExecutor()方法,我们可以轻松地自定义任务执行器。在应用程序中使用自定义任务执行器时,只需将其添加到异步方法所在的类上即可。例如:
@Service
public class MyService {@Async("myExecutor")public CompletableFuture<String> longRunningMethod() {// long running code here}
}
在上面的示例中,我们将@Async注解的value属性设置为"myExecutor",这是我们在AppConfig类中定义的自定义任务执行器的名称。
总之,通过自定义任务执行器,我们可以更好地控制异步任务的执行,从而提高应用程序的性能和可靠性。
4.1异常处理
前文介绍过,对于被@Async
注解的异步方法,只能返回void
或者Future
类型。对于返回Future
类型数据,如果异步任务方法抛出异常,则很容易进行处理,因为Future.get()
会重新抛出该异常,我们只需对其进行捕获即可。但是对于返回void
的异步任务方法,异常不会传播到被调用者线程,因此我们需要自定义一个额外的异步任务异常处理器,捕获异步任务方法抛出的异常。
4.1.1自定义一个异常处理器类实现接口如下所示:
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {@Overridepublic void handleUncaughtException(Throwable throwable, Method method, Object... objects) {System.out.println("Exception message - " + throwable.getMessage());System.out.println("Method name - " + method.getName());for (Object param : objects) {System.out.println("Parameter value - " + param);}}
}
4.1.2创建一个自定义Executor
异步配置类,将我们的自定义异常处理器设置到其接口上
@Configuration
@EnableAsync
public class AsyncConfigure implements AsyncConfigurer {@Overridepublic AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return new CustomAsyncExceptionHandler();}
}
此时异步方法如果抛出异常,就可以被我们的自定义异步异常处理器捕获得到。
4.1.3测试
@Service
public class AsyncService {@Async("methodLevelExecutor1")public void t1() throws InterruptedException {// 模拟耗时任务Thread.sleep(TimeUnit.SECONDS.toMillis(5));throw new NullPointerException();}
4.2异常处理(第二种方式)
SpringBoot异步任务的异常可以通过@Async注解的exceptional属性捕获。将exceptional属性设置为要捕获的异常类型即可。例如,以下示例演示如何捕获RuntimeException:
@Async(exceptional = RuntimeException.class)
public CompletableFuture<String> sendEmail() {// send email code here that may throw a RuntimeException
}
在上面的示例中,如果sendEmail()方法抛出RuntimeException,则该异常将被捕获并包装在CompletableFuture对象中返回。
另一种捕获异步任务异常的方法是使用@Async注解的异步方法的返回类型。如果异步方法的返回类型是CompletableFuture,则可以使用CompletableFuture的exceptionally()方法捕获异步任务的异常。例如,以下示例演示如何使用CompletableFuture的exceptionally()方法捕获异步任务的异常:
@Async
public CompletableFuture<String> sendEmail() {// send email code here that may throw a RuntimeException
}CompletableFuture<String> future = sendEmail();
future.exceptionally(ex -> {// handle the exception herereturn "Error sending email: " + ex.getMessage();
});
在上面的示例中,如果sendEmail()方法抛出RuntimeException,则可以在future.exceptionally()方法中捕获该异常并处理它。注意,exceptionally()方法返回一个新的CompletableFuture对象,该对象包含捕获到的异常或原始异步操作的结果。
总之,SpringBoot提供了多种捕获异步任务异常的方法,包括使用@Async注解的exceptional属性和CompletableFuture对象的exceptionally()方法。这些方法可以帮助我们更好地管理和处理异步任务的异常,提高应用程序的可靠性和稳定性。
相关文章:

springboot(10)异步任务
文章目录1、SpringBoot异步任务1.1使用注解EnableAsync开启异步任务支持1.2使用Async注解标记要进行异步执行的方法1.3controller测试2.异步任务相关限制3.1自定义 Executor3.1.1应用层级:3.1.2方法层级:3.2自定义 Executor (第二种方式)4.1异常处理4.1.…...

清华大学开源的chatGLM-6B部署实战
Windows部署 win10 通过wsl部署 常见问题: torch.cuda.OutOfMemoryError: CUDA out of memory. 在Windows的系统环境变量中增加 变量名:PYTORCH_CUDA_ALLOC_CONF 变量值:max_split_size_mb:32 文档书写时使用3090 24G显存配置,其他规格酌情调整 32 至其他值,如未设置变…...

通过矩阵从整体角度搞懂快速傅里叶变换原理
离散傅里叶变换公式 公式 f[k]∑n0N−1g[n]e−i(2π/N)kn,其中(0<n<N)f[k]\sum_{n0}^{N-1}g[n]e^{-i(2\pi/N)kn}, 其中(0<n<N) f[k]n0∑N−1g[n]e−i(2π/N)kn,其中(0<n<N) 逆变换公式 g[n]1N∑k0N−1f[k]ei(2π/N)kn,其中(0<k<N)g[n]\frac{1}{N}\…...

【C++从0到1】25、C++中嵌套使用循环
C从0到1全系列教程 1、实例代码 #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。int main() {// 超女分4个小组,每个小组有3名超女,在控制台显示每个超女的小组编号和组内编号。// 用一个循环…...

FastDFS与Nginx结合搭建文件服务器,并内网穿透实现公网访问
文章目录前言1. 本地搭建FastDFS文件系统1.1 环境安装1.2 安装libfastcommon1.3 安装FastDFS1.4 配置Tracker1.5 配置Storage1.6 测试上传下载1.7 与Nginx整合1.8 安装Nginx1.9 配置Nginx2. 局域网测试访问FastDFS3. 安装cpolar内网穿透4. 配置公网访问地址5. 固定公网地址5.1 …...

密集场景下的行人跟踪替代算法,头部跟踪算法 | CVPR 2021
一个不知名大学生,江湖人称菜狗 original author: Jacky LiEmail : 3435673055qq.com Time of completion:2023.4.8 Last edited: 2023.4.8 目录 摘要 主要内容 结果 这篇文章是CVPR 2021 的最新论文,文章的标题: 文章的主要内…...

Matlab与ROS(1/2)---服务端和客户端数据通信(五)
0. 简介 在前几讲我们讲了Matlab中的Message以及Topic的相关知识。而ROS主要支持的通信机制还有服务这一类。服务通过允许请求以及响应的通信方式,来给整个系统完成更紧密的耦合。服务客户端向服务服务器发送请求消息并等待响应。服务器将使用请求中的数据构造响应…...

数字化转型的避坑指南:细说数字化转型十二大坑
随着信息技术的快速发展,数字化转型已经成为许多企业发展的必经之路。然而,数字化转型过程中也存在许多坑,如果不谨慎处理,就可能导致企业陷入困境。本文将细说数字化转型的十二大坑,并提供相应的避坑指南。 1、不了解…...

pt05Encapsulationinherit
Encapsulation &inherit 封装继承 封装 向类外提供必要的功能,隐藏实现的细节, 代码可读性更高优势:简化编程,使用者不必了解具体的实现细节,只需要调用对外提供的功能。私有成员:作用:无需向类外提供…...

面向对象编程(基础)9:封装性(encapsulation)
目录 9.1 为什么需要封装? 而“高内聚,低耦合”的体现之一: 9.2 何为封装性? 9.3 Java如何实现数据封装 9.4 封装性的体现 9.4.1 成员变量/属性私有化 实现步骤: 成员变量封装的好处: 9.4.2 私有化…...

fate-serving-server增加取数逻辑并源码编译
1.什么是fate-serving-server? FATE-Serving 是一个高性能、工业化的联邦学习模型服务系统,专为生产环境而设计,主要用于在线推理。 2.fate-serving-server源码编译 下载fate-serving-serving项目(GitHub - FederatedAI/FATE-Serving: A scalable, h…...

循环队列、双端队列 C和C++
队列 目录 概念 实现方式 顺序队列 循环队列 队列的数组实现 用循环链表实现队列 STL 之 queue 实现队列 STL 之 dequeue 实现双端队列 概念 队列是一种特殊的线性表,它只允许在表的前端(称为队头,front)进行删除操作…...

正则表达式(语法+例子)
文章目录一、介绍二、语法1、匹配字符2、表示数量的字符3、边界字符4、其他字符5、转义字符三、例子1、邮箱2、用逗号分隔的数字集合1,23、允许一位小数4、20yy-mm-dd日期格式5、手机号6、匹配html、xml标签一、介绍 正则表达式(Regular Expression)&am…...

Properties和IO流集合的方法
方法名说明void load(InputStream inStream)从输入字节流读取属性列表(键和元素)void load(Reader reader)从输入字符流读取属性列表(键和元素对)void store(OutputStream out,String comments)将此属性列表(键和元素对…...

python 生成器、迭代器、动态新增属性及方法
目录 一、生成器 1、生成器定义 2、生成器存在的意义 3、创建生成器方式一(生成器表达式) 4. 创建生成器方式二(生成器函数) 1. 生成器函数 2. 生成器函数的工作原理 5. 总结 1. 什么是生成器 2. 生成器特点 二、迭代器…...

Java处理JSON
Java处理json有很多种方法,在这里总结一下。 1 Jackson Spring MVC 默认采用Jackson解析Json,出于最小依赖的考虑,也许Json解析第一选择就应该是Jackson。 1.1 引入的包 Jackson核心模块由三部分组成:jackson-core、jackson-a…...

58-Map和Set练习-LeetCode692前k个高频单词
题目 给定一个单词列表 words 和一个整数 k ,返回前 k 个出现次数最多的单词。 返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序 排序。 示例 1: 输入: words ["i", "love", …...

线程生命周期及五种状态
文章目录一、线程生命周期及五种状态1、New(初始化状态)2、Runnable(就绪状态)3、Running(运行状态)4、Blocked(阻塞状态)5、Terminated(终止状态)二、线程基本方法1、线程等待(wait)2、线程睡眠(sleep)3、…...

OBCP第八章 OB运维、监控与异常处理-灾难恢复
灾难恢复是指当数据库中的数据在被有意或无意破坏后复原数据库所需要执行的活动 回收站:回收站在原理上说就是一个数据字典表,放置用户删除的数据库对象信息。用户删除的东西被放入回收站后,其实仍然占据着物理空间,除非您手动进…...

亚马逊云科技Serverless Data:数字经济下的创新动能
Serverless时代已经到来!企业的技术架构,总是伴随着不断增长的数据与日趋复杂的业务持续演进。如何通过构建更易用的技术架构来聚焦在业务本身,而不必在底层基础设施的管理上投入过多的精力,是数据驱动型企业需要思考的重要议题。…...

【Ruby学习笔记】15.Ruby 异常
Ruby 异常 异常和执行总是被联系在一起。如果您打开一个不存在的文件,且没有恰当地处理这种情况,那么您的程序则被认为是低质量的。 如果异常发生,则程序停止。异常用于处理各种类型的错误,这些错误可能在程序执行期间发生&…...

聊聊MySQL主从延迟
文章目录 MySQL 的高可用是如何实现的呢?二、什么是主备延迟?三、主备延迟常见原因1、备库机器配置差2、备库干私活3、大事务四、主库不可用,主备切换有哪些策略?1、可靠优先2、可用优先实验一实验二3、结论MySQL 的高可用是如何实现的呢? 高可用性(high availability,缩…...

【C++从0到1】19、C++中多条件的if语句
C从0到1全系列教程 1、多条件的if语句 语法: if (表达式一) { // 表达式一为真时执行的语句。 } else if (表达式二) {// 表达式二为真时执行的语句。 } else if (表达式三) {// 表达式三为真时执行的语句。 } …… else if (表达式n) {// 表达式n为真时执行的语句。…...

【多微电网】计及碳排放的基于交替方向乘子法(ADMM)的多微网电能交互分布式运行策略研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

Linux(centos7)安装防火墙firewalld及开放端口相关命令
安装firewalld 防火墙命令: yum install firewalld 安装完成,查看防火墙状态为 not running,即未运行,输入命令开启: 添加开放端口: 防火墙相关命令: 查看防火墙状态 systemctl status firewa…...

Linux部署.Net Core Web项目
本文主要记录我在Linux(Ubuntu)上部署.net core 的操作记录,也便于以后部署。 如对您有所帮助,不胜荣幸~ 文章目录前言一、准备工作1. 版本信息2. windows端web项目二、操作步骤1. Linux 配置 .net 运行环境1.1 查看最新 .net 运行环境的下载路径1.2 安装…...

【C++】STL之stack、queue的使用和模拟实现+优先级队列(附仿函数)+容器适配器详解
之前的一段时间,我们共同学习了STL中一些容器,如string、vector和list等等。本章我们将步入新阶段的学习——容器适配器。本章将详解stack、queue的使用和模拟实现优先级队列(附仿函数)容器适配器等。 目录 (一&…...

第⑦讲:Ceph集群RGW对象存储核心概念及部署使用
文章目录1.RadosGW对象存储核心概念1.1.什么是RadosGW对象存储1.2.RGW对象存储架构1.3.RGW对象存储的特点1.4.对象存储中Bucket的特性1.4.不同接口类型的对象存储访问对比2.在集群中部署RadosGW对象存储组件2.1.部署RGW组件2.2.集群中部署完RGW组件后观察集群的信息状态2.3.修改…...

从异步到promise
一,背景 1.1,js的单线程 这一切,要从js诞生之初说起,因为js是单线程的语言。 js单线程原因:作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程&…...

Linux系统中进行JDK环境的部署
一、为什么需要部署JDK。 JDK:Java Development Kit,是用于Java语言开发的环境。 部署JDK不需要懂得Java语言,只需要掌握Linux相关命令即可。 二、部署版本与环境。 系统:安装在VMware环境下的CentOS7.6; JDK版本&a…...