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 ...
随机推荐
- Vue2.x项目整合ExceptionLess监控
前言 一直以来我们都是用Sentry做项目监控,不过前段时间我们的Sentry坏掉了(我搞坏的) 但监控又是很有必要的,在sentry修好之前,我想先寻找一个临时的替代方案,同时发现网上关于Excep ...
- [转帖]深入了解 gRPC:协议
https://cn.pingcap.com/blog/grpc 经过很长一段时间的开发,TiDB 终于发了 RC3.RC3 版本对于 TiKV 来说最重要的功能就是支持了 gRPC,也就意味着后面大 ...
- [转帖]jmeter_采样器sampler简介
1.取样器介绍 取样器是用来模拟用户操作的,向服务器发送请求以及接收服务器的响应数据. 取样器是在线程组内部的元件,也就是说取样器只能在线程组中添加. 取样器(Sampler)是性能测试中向服务器发送 ...
- [转帖]gcc -O0 -O1 -O2 -O3 四级优化选项及每级分别做什么优化
相关博客http://blog.chinaunix.net/uid-24954950-id-2956476.html 相关博客http://blog.csdn.net/misiter/article/ ...
- [转帖]Linux 防火墙开放特定端口 (iptables)
查看状态: iptables -L -n 下面添加对特定端口开放的方法: 使用iptables开放如下端口 /sbin/iptables -I INPUT -p tcp --dport 8000 -j ...
- Loki动态展示linux本地日志
Loki动态展示linux本地日志 背景 产品需要拆分微服务部署,直接使用K8S部署虽然比较规范但是部署时间较长. 本地文件系统部署简洁快速一些, 但是不太好直接复用一些规范的产品. 本次处理方法就是 ...
- 巧用GenericObjectPool创建自定义对象池
作者:京东物流 高圆庆 1 前言 通常一个对象创建.销毁非常耗时的时候,我们不会频繁的创建和销毁它,而是考虑复用.复用对象的一种做法就是对象池,将创建好的对象放入池中维护起来,下次再用的时候直接拿池中 ...
- CCPC Finals 2021 H Harie Programming Contest (网络流&支配树的妙用)
Link 题意: 给一个二分图,求有多少种方案删去恰好两个点,使得最大匹配数不变.\(n,m\le 2\times 10^5\). 二话不说先跑一遍 Dinic 网络流,设残量网络形成的图为 \(G\ ...
- chaincode中使用第三方库
本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处. 在fabric的chaincode开发时,有时候需要用到第三方库提供的功能.这 ...
- QAnything本地知识库问答系统:基于检索增强生成式应用(RAG)两阶段检索、支持海量数据、跨语种问答
QAnything本地知识库问答系统:基于检索增强生成式应用(RAG)两阶段检索.支持海量数据.跨语种问答 QAnything (Question and Answer based on Anythi ...