关于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. [转帖]CoreDNS loop 插件异常问题

    https://zhuanlan.zhihu.com/p/476611162   背景 最近有遇到一个客户集群,发现集群中的 CoreDNS 老是异常 (loop 插件检测到有回路后进行 panic) ...

  2. [转帖]为什么需要在脚本文件的开头加上#!/ bin / bash?

    本文翻译自:Why do you need to put #!/bin/bash at the beginning of a script file? I have made Bash scripts ...

  3. [转帖]sql server 索引阐述系列六 碎片查看与解决方案

    https://www.cnblogs.com/MrHSR/p/9365720.html 一 . dm_db_index_physical_stats 重要字段说明 1.1 内部碎片:是avg_pag ...

  4. [转帖]JMeter InfluxDB v2.0 listener plugin

    https://github.com/mderevyankoaqa/jmeter-influxdb2-listener-plugin Support my Ukrainian Family ️ Lik ...

  5. [转帖]大模型训练,英伟达Turing、Ampere和Hopper算力分析

    https://www.eet-china.com/mp/a219195.html 大 GPU 优势在于通过并行计算实现大量重复性计算.GPGPU即通用GPU,能够帮助 CPU 进行非图形相关程序的运 ...

  6. 你对iframe知道多少

    iframe 嵌套第三方页面出现的问题 我们需要通过一个接口获取被嵌套的地址. 然后将改地址赋值给iframe的src中,代码如下 <template> <div> <i ...

  7. 替换 &开头。;结尾之间的内容。用空格代替他们

    替换 &开头.;结尾之间的内容.用空格代替他们 var regExp = /\&.*?\;/g; var str = '123&asdsa;dqwe'; str = str.r ...

  8. Vue基础系列文章10---单文件组件

    1.单文件组件的结构 <template> <!--这里用于定义VUE组件的模块内容--> <dvi> <h1>这是 APP 根组件</h1> ...

  9. CANVAS ----- 鼠标移动画圆

    1.增加鼠标移动事件 $('#canvas').mousemove(function (e) { draw(event); }); 2.获取鼠标在canvas上的坐标 function getCanv ...

  10. hadoop实践02---eclipse操作hdfs的api上传文件

    1.eclipse中编写代码后双击main方法--->Run as ---> java application ,然后指定的文件 就会提交到hdfs中. 2.查看文件:http://192 ...