关于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();
}
}

启动程序测试

  1. 浏览器输入:http://localhost:8080/register
  • 控制台输出,发现注册和发送邮件分别由2个不同的线程处理!

    2.浏览器输入:http://localhost:8080/registerWithResult
  • 发现虽然也是异步执行,由于Future.get方法阻塞了主线程导致10s后主线程才返回结果。
  1. 浏览器输入:http://localhost:8080/registerWithAsyncResult
  • 发现异步任务执行正常,且拿到了自定义的回调处理信息

Spring异步任务async介绍与案例实战的更多相关文章

  1. 关于Dubbo和Spring异步注解@Async的冲突

    项目中难免会有异步处理的需求,像异步记录日志啦,异步发送邮件啦,而Dubbo又是现在主流的分布式框架,所有异步+Dubbo的组合是再所难免的 但博主是实践中发现Dubbo的服务并不能很好的跟Sprin ...

  2. Spring异步执行(@Async)2点注意事项

    Spring中可以异步执行代码,注解方式是使用@Async注解. 原理.怎么使用,就不说了. 写2点自己遇到过的问题. 1.方法是公有的 // 通知归属人 @Async public void not ...

  3. 工作随笔——spring异步处理@Async使用笔记

    @Async使用笔记 必须是public方法 必须是非static方法 方法调用的实例必须由spring创建和管理 代码示例如下: // 创建Foo类@Component class Foo { @A ...

  4. Spring异步-@Async注解

    Spring异步:@Async注解 使用@Async前需要开启异步支持:@EnableAsync 注解和XML方式 @Async返回值的调用:需要使用Future包装 1.如果没有使用Future包装 ...

  5. 异步注解@Async使用及其部分调优

    对于spring异步注解@Async的使用: 对于异步方法调用,从Spring3开始提供了@Async注解,该注解可以被标注在方法上,以便异步地调用该方法.调用者将在调用时立即返回,方法的实际执行将提 ...

  6. spring中使用@Async注解进行异步处理

    引言: 在Java应用中,绝大多数情况下都是通过同步的方式来实现交互处理的:但是在处理与第三方系统交互的时候,容易造成响应迟缓的情况,之前大部分都是使用多线程来完成此类任务,其实,在spring 3. ...

  7. Spring中异步注解@Async的使用、原理及使用时可能导致的问题

    前言 其实最近都在研究事务相关的内容,之所以写这么一篇文章是因为前面写了一篇关于循环依赖的文章: <面试必杀技,讲一讲Spring中的循环依赖> 然后,很多同学碰到了下面这个问题,添加了S ...

  8. spring boot / cloud (四) 自定义线程池以及异步处理@Async

    spring boot / cloud (四) 自定义线程池以及异步处理@Async 前言 什么是线程池? 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线 ...

  9. node js 异步运行流程控制模块Async介绍

    1.Async介绍 sync是一个流程控制工具包.提供了直接而强大的异步功能.基于Javascript为Node.js设计,同一时候也能够直接在浏览器中使用. Async提供了大约20个函数,包含经常 ...

  10. 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 ...

随机推荐

  1. unix domain 与本地本地回环在进程间通信中的差异

    前言: 127.0.0.1它是一个私有IP,代表的就是你的本机环回地址,其实本质上是绑定在虚拟网卡loopback上的IP. 在实际应用中,有遇到在使用本地回环做进程间通讯的时候程序阻塞的情况.比如下 ...

  2. 【scikit-learn基础】--『回归模型评估』之偏差分析

    模型评估在统计学和机器学习中具有至关重要,它帮助我们主要目标是量化模型预测新数据的能力. 本篇主要介绍模型评估时,如何利用scikit-learn帮助我们快速进行各种偏差的分析. 1. **R² ** ...

  3. Grafana针对内存监控值的学习与使用

    Grafana针对内存监控值的学习与使用 背景 因为学习内存相关的知识, 可以通过pgcacher/sar -r 等命令监控系统信息. 但是现在发现. 不太直观, 所以想着使用别的方式来进行处理. 然 ...

  4. 编译打包rabbitmq然后一键部署的简单方法

    摘要 之前总结过一版,但是感觉不太全面 想着本次能够将使用中遇到的问题总结一下. 所以本次是第二版 介质下载 rabbitmq 不区分介质的打包文件 rabbitmq-server-generic-u ...

  5. qperf 简要总结 - 延迟与带宽信息

    总结 同一个虚拟机: 延迟: 12us 带宽: 6GB/S 同一个物理机上面的虚拟机: 延迟: 50us-100us 带宽: 1.2GB/S 同一个交换机上面的虚拟机: 延迟: 60us 带宽: 12 ...

  6. 【小分享】vm-storage中,计算metric的平均长度

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 表达式如下: sum by (type) (vm_cach ...

  7. 在cmd(命令行)或bat文件切换盘符

    bat文件 写一个自动更新git的bat文件,如果bat文件放在E盘,想要去到D盘的某个目录下执行命令,代码如下: SET ksf=D:\code\KSFramework @echo on d: cd ...

  8. 6张图表 + 1个案例 带你入门tcpdump的使用和原理

    一.tcpdump简介 tcpdump是什么? 来看看 tcpdump官网怎么说:This is the home web site of tcpdump, a powerful command-li ...

  9. Prompt learning 教学基础篇:prompt基本原则以及使用场景技巧助力你更好使用chatgpt,得到你想要的答案

    Prompt learning 教学[基础篇]:prompt基本原则以及使用场景技巧助力你更好使用chatgpt,得到你想要的答案 如果你想系统学习 如果你对 AI 和 Prompt Engineer ...

  10. 解决: DECODER_ERROR_CLASSES += (brotli.error,) ttributeError: module ‘brotli‘ has no attribute ‘error‘

    解决: DECODER_ERROR_CLASSES += (brotli.error,) ttributeError: module 'brotli' has no attribute 'error' ...