Spring异步任务async介绍与案例实战
创始人
2024-03-15 20:16:24
0

关于spring异步任务

简单地说,用@Async注释bean的方法将使其在单独的线程中执行。换句话说,调用者不会等待被调用方法的完成。利用spring提供的注解即可简单轻松的实现异步任务处理。

默认线程池问题

Spring 异步任务默认使用 Spring 内部线程池 SimpleAsyncTaskExecutor 这个线程池比较坑爹,不会复用线程。也就是说来一个请求,将会新建一个线程。极端情况下,如果调用次数过多,将会创建大量线程。

Java 中的线程是会占用一定的内存空间 ,所以创建大量的线程将会导致 OOM 错误。
所以如果需要使用异步任务,我们需要一定要使用自定义线程池替换默认线程池。

实战案例

此处以用户注册同时发邮件为例,将发送邮件设置为异步任务。

创建配置类

  • 该配置类中包括了统一异常处理自定义线程池
@Slf4j
@EnableAsync    // 开启 Spring 异步任务支持
@Configuration
public class AsyncPoolConfig implements AsyncConfigurer {/*** 

将自定义的线程池注入到 Spring 容器中

* */@Bean(name = "threadPoolTaskExecutor")@Overridepublic 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;}/***

指定系统中的异步任务在出现异常时使用到的处理器

* */@Overridepublic AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return new AsyncExceptionHandler();}/***

异步任务异常捕获处理器

* */@SuppressWarnings("all")class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {@Overridepublic 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 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 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 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 listenableFuture = emailService.sendEmailWithAsyncResult();// 添加异步回调逻辑listenableFuture.addCallback(new SuccessCallback() {@Overridepublic void onSuccess(String result) {log.info("发送邮件成功,异步回调结果:" + result);}}, new FailureCallback() {@Overridepublic 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
  • 发现异步任务执行正常,且拿到了自定义的回调处理信息
    在这里插入图片描述

在这里插入图片描述

相关内容

热门资讯

【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
ChatGPT 怎么用最新详细... ChatGPT 以其强大的信息整合和对话能力惊艳了全球,在自然语言处理上面表现出了惊人...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...