Spring异步任务async介绍与案例实战
关于spring异步任务
简单地说,用@Async注释bean的方法将使其在单独的线程中执行。换句话说,调用者不会等待被调用方法的完成。利用spring提供的注解即可简单轻松的实现异步任务处理。
默认线程池问题
Spring 异步任务默认使用 Spring 内部线程池 SimpleAsyncTaskExecutor 这个线程池比较坑爹,不会复用线程。也就是说来一个请求,将会新建一个线程。极端情况下,如果调用次数过多,将会创建大量线程。
Java 中的线程是会占用一定的内存空间 ,所以创建大量的线程将会导致 OOM 错误。
所以如果需要使用异步任务,我们需要一定要使用自定义线程池替换默认线程池。
实战案例
此处以用户注册同时发邮件为例,将发送邮件设置为异步任务。
创建配置类
- 该配置类中包括了
统一异常处理、自定义线程池
@Slf4j
@EnableAsync // 开启 Spring 异步任务支持
@Configuration
public class AsyncPoolConfig implements AsyncConfigurer {
/**
* <h2>将自定义的线程池注入到 Spring 容器中</h2>
* */
@Bean(name = "threadPoolTaskExecutor")
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(20);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("My-Async-"); // 这个非常重要
// 等待所有任务结果候再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
// 定义拒绝策略
executor.setRejectedExecutionHandler(
new ThreadPoolExecutor.CallerRunsPolicy()
);
// 初始化线程池, 初始化 core 线程
executor.initialize();
return executor;
}
/**
* <h2>指定系统中的异步任务在出现异常时使用到的处理器</h2>
* */
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncExceptionHandler();
}
/**
* <h2>异步任务异常捕获处理器</h2>
* */
@SuppressWarnings("all")
class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable throwable, Method method,
Object... objects) {
throwable.printStackTrace();
log.error("Async Error: [{}], Method: [{}], Param: [{}]",
throwable.getMessage(), method.getName(),
JSON.toJSONString(objects));
// TODO 发送邮件或者是短信, 做进一步的报警处理
}
}
}
创建EmailService
@Slf4j
@Service
public class EmailService {
// 模拟发送邮件
@Async("threadPoolTaskExecutor")
public void sendEmail(){
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("sendEmail on a thread named: [{}]", Thread.currentThread().getName());
}
// 带返回类型的方法,获取返回值阻塞方式
@Async("threadPoolTaskExecutor")
public Future<String> sendEmailWithResult(){
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new AsyncResult<>("sendEmailWithResult on a thread named: "+Thread.currentThread().getName());
}
// 带返回类型的方法,获取返回值非阻塞
@Async("threadPoolTaskExecutor")
public ListenableFuture<String> sendEmailWithAsyncResult(){
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new AsyncResult<>("sendEmailWithAsyncResult on a thread named: "+Thread.currentThread().getName());
}
}
创建UserController
@Slf4j
@RestController
public class UserController {
private EmailService emailService;
public UserController(EmailService emailService){
this.emailService = emailService;
}
// 使用异步方式耗时
@GetMapping("/register")
public String register(){
long start = System.currentTimeMillis();
emailService.sendEmail();
long end = System.currentTimeMillis();
return "User register took "+(end -start) +" milliseconds on thread named: "+Thread.currentThread().getName();
}
// 使用带有返回值的异步方式
@GetMapping("/registerWithResult")
public String registerWithResult(){
long start = System.currentTimeMillis();
Future<String> future = emailService.sendEmailWithResult();
try {
// Future#get 方法将会一直阻塞,直到异步任务执行成功
String result = future.get();
log.info(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
return "registerWithResult on thread named: "+Thread.currentThread().getName();
}
// 使用带有返回值的异步方式
@GetMapping("/registerWithAsyncResult")
public String registerWithAsyncResult(){
ListenableFuture<String> listenableFuture = emailService.sendEmailWithAsyncResult();
// 添加异步回调逻辑
listenableFuture.addCallback(new SuccessCallback<String>() {
@Override
public void onSuccess(String result) {
log.info("发送邮件成功,异步回调结果:" + result);
}
}, new FailureCallback() {
@Override
public void onFailure(Throwable ex) {
log.info("发送邮件失败,异步回调异常:" + ex);
}
});
return "registerWithAsyncResult on thread named: "+Thread.currentThread().getName();
}
}
启动程序测试
- 浏览器输入:http://localhost:8080/register

- 控制台输出,发现注册和发送邮件分别由2个不同的线程处理!

2.浏览器输入:http://localhost:8080/registerWithResult

- 发现虽然也是异步执行,由于Future.get方法阻塞了主线程导致10s后主线程才返回结果。

- 浏览器输入:http://localhost:8080/registerWithAsyncResult
- 发现异步任务执行正常,且拿到了自定义的回调处理信息


Spring异步任务async介绍与案例实战的更多相关文章
- 关于Dubbo和Spring异步注解@Async的冲突
项目中难免会有异步处理的需求,像异步记录日志啦,异步发送邮件啦,而Dubbo又是现在主流的分布式框架,所有异步+Dubbo的组合是再所难免的 但博主是实践中发现Dubbo的服务并不能很好的跟Sprin ...
- Spring异步执行(@Async)2点注意事项
Spring中可以异步执行代码,注解方式是使用@Async注解. 原理.怎么使用,就不说了. 写2点自己遇到过的问题. 1.方法是公有的 // 通知归属人 @Async public void not ...
- 工作随笔——spring异步处理@Async使用笔记
@Async使用笔记 必须是public方法 必须是非static方法 方法调用的实例必须由spring创建和管理 代码示例如下: // 创建Foo类@Component class Foo { @A ...
- Spring异步-@Async注解
Spring异步:@Async注解 使用@Async前需要开启异步支持:@EnableAsync 注解和XML方式 @Async返回值的调用:需要使用Future包装 1.如果没有使用Future包装 ...
- 异步注解@Async使用及其部分调优
对于spring异步注解@Async的使用: 对于异步方法调用,从Spring3开始提供了@Async注解,该注解可以被标注在方法上,以便异步地调用该方法.调用者将在调用时立即返回,方法的实际执行将提 ...
- spring中使用@Async注解进行异步处理
引言: 在Java应用中,绝大多数情况下都是通过同步的方式来实现交互处理的:但是在处理与第三方系统交互的时候,容易造成响应迟缓的情况,之前大部分都是使用多线程来完成此类任务,其实,在spring 3. ...
- Spring中异步注解@Async的使用、原理及使用时可能导致的问题
前言 其实最近都在研究事务相关的内容,之所以写这么一篇文章是因为前面写了一篇关于循环依赖的文章: <面试必杀技,讲一讲Spring中的循环依赖> 然后,很多同学碰到了下面这个问题,添加了S ...
- spring boot / cloud (四) 自定义线程池以及异步处理@Async
spring boot / cloud (四) 自定义线程池以及异步处理@Async 前言 什么是线程池? 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线 ...
- node js 异步运行流程控制模块Async介绍
1.Async介绍 sync是一个流程控制工具包.提供了直接而强大的异步功能.基于Javascript为Node.js设计,同一时候也能够直接在浏览器中使用. Async提供了大约20个函数,包含经常 ...
- MVC+Spring.NET+NHibernate .NET SSH框架整合 C# 委托异步 和 async /await 两种实现的异步 如何消除点击按钮时周围出现的白线? Linq中 AsQueryable(), AsEnumerable()和ToList()的区别和用法
MVC+Spring.NET+NHibernate .NET SSH框架整合 在JAVA中,SSH框架可谓是无人不晓,就和.NET中的MVC框架一样普及.作为一个初学者,可以感受到.NET出了MV ...
随机推荐
- Go-数据类型-布尔类型-bool
布尔类型--bool 只有两个值 true false 不能转换成其他类型,也不能参加数值运算 布尔应用场景 if swicht for 三大分支语句中的条件部分 比较运算和逻辑运算的返回值 作为通道 ...
- [转帖]SkyWalking告警使用
SkyWalking告警 SkyWalking提供了强大的监控告警功能,在监控到应用出现问题的时候,会调用webhook或者gRPC hook或者Wechat DingDing等工具报告警告信息 而且 ...
- [转帖]Java程序在K8S容器部署CPU和Memory资源限制相关设置
2019-04-297279 版权 本文涉及的产品 容器服务 Serverless 版 ACK Serverless,317元额度 多规格 推荐场景: 立即试用 容器镜像服务 ACR,镜像仓库100个 ...
- [转帖]Kingbase实现Oracle userenv函数功能
目录 1. 问题 2. 文档概述 3. Oracle userenv()函数功能调研 3.1. 函数名称/函数原型 3.2. 函数功能 3.3. 参数介绍 3.3.1. Parameter 3.4. ...
- 【转帖】《MySQL高级篇》四、索引的存储结构
1. 为什么使用索引 假如给数据使用 二叉树 这样的数据结构进行存储,如下图所示 2.索引及其优缺点 2.1 索引概述 2.2 优点 类似大学图书馆建书目索引,提高数据检索的效率,降低 数据库的 IO ...
- [转帖]【rpm】源码包制作rpm包|修改rpm、重新制作rpm包
目录 前言 安装rpmbuild rpmbuild制作rpm 包 同时生成devel包 阻止rpmbuid打包时strip程序/库 修改rpm.重新制作rpm包 RPM 打包 工具 SPEC文件 sp ...
- 【转帖】15.JVM栈帧的内部结构
目录 1.栈中存储的是什么? 2.栈的运行原理 1.栈中存储的是什么? 1.每个线程都有自己的栈,栈中存储的是栈帧. 2.在这个线程上正在执行的每个方法都各自对应一个栈帧.方法与栈帧是一对一的关系. ...
- Windows 修改时间提示: 某些设置已隐藏或由你的组织管理 的解决方案
最近公司的一台生产服务器时间不对. 因为机器有域控的需求, 所以加入了域, 想改时间时有这样的提示信息: 某些设置已隐藏或由你的组织管理 百度了很久发现没有解决方法.. 但是突然发现可以使用 运行-& ...
- IPV6的简单学习与整理
背景 大概2018年时曾经突击学习过一段时间IPV6 当时没太有写文档的习惯,导致这边没有成型的记录了. 今天又有项目要求使用IPV6, 想了想就将之前学习的部分 还有想继续学习提高的部分进行一下总结 ...
- vue 半场动画进入状态
<style> .box{ width: 30px; height: 30px; border-radius: 50%; background: red; } </style> ...