Spring异步任务async介绍与案例实战

news2024/9/20 0:51:47

关于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
  • 发现异步任务执行正常,且拿到了自定义的回调处理信息
    在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/58624.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网页自动点赞

进入网页版QQ空间 ,在开发者模式下创建脚本 var x5,y10;function autoClick(){yy5;var zandocument.getElementsByClassName(item qz_like_btn_v3);for(var i0;i<zan.length;i){if(zan[i].attributes[6].valuelike){zan[i].firstChild.click();}};window.scrollBy(x,y);}win…

LC118原厂直流驱动芯片 SOP-8

直流驱动芯片 LC118 SOP-8 电机驱动电路 马达驱动IC 马达驱动IC LC118的描述&#xff1a; LC118 是为专门为低电压下工作的系统而设计的单通道玩具直流电机驱动集成电路。它具有 H桥驱动器, 采用导通电阻非常小的P-MOS和N-MOS功率晶体管作为开关管&#xff0c;可在瞬间提供大电…

CMake中target_link_libraries的使用

CMake中的target_link_libraries命令用于指定链接给定目标和/或其依赖项时要使用的库或标志。来自链接库目标的使用要求将被传播(propagated)。目标依赖项的使用要求会影响其自身源代码的编译。其格式如下&#xff1a; target_link_libraries(<target> ... <item>…

用Rust写一个链表,非常详细,一遍看懂

前言 在Rust里写一个链表可不是一件容易的事&#xff0c;涉及到很多的知识点&#xff0c;需要熟练掌握之后才能写出一个不错的链表。这篇文章主要介绍了如何写一个Rust链表&#xff0c;并且补充了涉及到的很多的额外知识点&#xff0c;尤其是所有权问题。 首先&#xff0c;你需…

人工智能内容生成元年—AI绘画原理解析

AIGC体验生成一、背景 2022年AIGC&#xff08;AI生成内容&#xff09;焕发出了勃勃生机&#xff0c;大有元年之势&#xff0c;技术与应用迭代都扎堆呈现。在各种新闻媒体处可以看到诸多关于学术前沿研究&#xff0c;以及相应落地的商用案例。可谓出现了现象级的学术-商业共振。…

phpbugs代码审计第三题多重加密答案错误

phpbugs多重加密 这题官方给的答案是错的&#xff0c;网上给的也是错的&#xff0c;麻烦别直接拿给的答案来抄好吗&#xff1f;就想纠正一下别误导别人了 源码如下: <?phpinclude common.php;$requset array_merge($_GET, $_POST, $_SESSION, $_COOKIE);//把一个或多个…

安卓期末大作业——售票APP源码和设计报告

大作业文档 项目名称&#xff1a;售票APP专业&#xff1a;班级&#xff1a;学号&#xff1a;姓名&#xff1a; 目 录 目录 一、项目功能介绍3二、项目运行环境31、开发环境32、运行环境33、是否需要联网3三、项目配置文件及工程结构31、工程配置文件32、工程结构目录4四、项…

【C语言 数据结构】串 - 顺序

文章目录串 - 顺序一、串的表示及实现1.1 串的概念1.2 串的初始化1.3 复制串1.4 串的判空1.5 串的比较1.6 串的长度1.7 清空串1.8 串的拼接1.9 串的截取1.10 插入子串1.11 串的打印二、串的应用2.1 串的模式匹配 - 基本操作2.2 串的模式匹配 - BF2.3 串的模式匹配 - KMPnext数组…

【学习笔记77】ajax的函数封装

一、封装分析 &#xff08;一 &#xff09;封装方案 1、回调函数方式 将来使用的时候, 可能会遇到回调地狱 2、promise方式 效果不会有变化, 而且还能和 async/await 一起使用 &#xff08;二&#xff09;函数的参数 请求方式: method > 默认值 get请求地址: url > 必填…

Android开发——Jetpack Compose的使用

Android开发——Jetpack Compose的使用什么是Jetpack ComposeJetpack Compose带来的变化Jetpack Compose的两种运用方法将Jetpack Compose 添加到现有项目1.gradle 配置2.使用Kotlin-Gradle 插件3. 添加依赖项创建一个新的支持Jetpack Compose的项目1.创建一些简单应用定义可组…

12.3做题

一.车队问题 1.思路: 先把所在位置进行排序,升序排序, 计算出每辆车在不受其余车的影响时&#xff0c;行驶到终点需要的时间 从后往前看 对于相邻的两辆车 S 和 F&#xff0c;F 的起始位置大于 S&#xff0c;如果 S 行驶到终点需要的时间小于等于 F&#xff0c;那么 S 一定…

MySQL集群搭建-MMM高可用架构

MySQL集群搭建-MMM高可用架构 原文地址 https://segmentfault.com/a/1190000017286307&#xfeff; 1 MMM 介绍 1.1 简介 MMM 是一套支持双主故障切换以及双主日常管理的第三方软件。MMM 由 Perl 开发&#xff0c;用来管理和监控双主复制&#xff0c;虽然是双主架构&#xff…

volatile

是java虚拟机提供的轻量级的同步机制&#xff08;乞丐版的synchronized) 具备三个性质&#xff1a;保证可见性&#xff0c;不保证原子性&#xff0c;禁止指令重排 前置知识&#xff1a; …

JS读取本地CSV文件数据

JS读取本地CSV文件数据 文件中的部分数据如图 需求是需要提取出文件的数据 使用到的模块是 Papa Parse 1. 依赖安装 yarn add papaparse papaparse的基本使用可以参考官方demo 2. 解析本地文件 首先需要注意, papaparse解析本地文件, 需要的文件格式是从DOM中获得的File…

GO高级特性 之 并发模型

本文介绍一些并发的基础知识、常见的并发模型一级Go语言的MPG并发模型及其运行原理 并发与并行的区别 -并发并行概念并发指同一时间段&#xff0c;多条命令在CPU上同时执行。并行指同一时刻&#xff0c;多条命令在CPU上执行运行原理并发程序不要求计算机有多核计算能力&#…

毕设选题推荐基于python的django框架医疗急诊预约系统

&#x1f496;&#x1f525;作者主页&#xff1a;计算机毕设老哥&#x1f525; &#x1f496; 精彩专栏推荐订阅&#xff1a;在 下方专栏&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; Java实战项目专栏 Python实…

项目成本管理质量管理

项目成本管理-控制成本目录概述需求&#xff1a;设计思路实现思路分析1.EVM2.偏差指标3.Question:4.绩效指标5.典型偏差TCPIS曲线图绩效审查参考资料和推荐阅读Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip …

[近两万字] MySQL大全

目录 单元1 创建数据库 1.1 创建数据库 1.查看数据库 2.选择数据库 3.删除数据库 1.2 创建数据表 1.查看表结构 2.查看所有数据表 3.复制表结构 4.删除表 5.修改表数据 5.1 修改表名 5.2 添加字段 5.3删除字段 5.4修改字段的数据类型 5.5修改字段的名称 5.6修改字段…

[网络工程师]-应用层协议-SNMP

简单网络管理协议&#xff08;Simple Network Management Protocol&#xff0c;SNMP&#xff09;是在应用层上进行网络设备间通信的管理协议&#xff0c;可以用于网络状态监视、网络参数设定、网络流量统计与分析、发现网络故障等。SNMP基于UDP协议&#xff0c;由SNMP协议、管理…

【交通建模】基于模型的自主交通仿真框架附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …