@Async异步线程:Spring 自带的异步解决方案

前言 

        在项目应用中,使用MQ异步调用来实现系统性能优化,完成服务间数据同步是常用的技术手段。如果是在同一台服务器内部,不涉及到分布式系统,单纯的想实现部分业务的异步执行,这里介绍一个更简单的异步方法调用。

        对于异步方法调用,从Spring3 开始提供了@Async 注解,该注解可以被标注在方法上,以便异步地调用该方法。调用者将在调用时立即返回,而方法的实际执行将提交给 Spring TaskExecutor 的任务中,由指定的线程池中的线程执行。

        本文讲述了@Async注解在Spring体系中的简单应用,仅供学习,欢迎意见反馈。  


正文

一、Spring线程池的分类

        以下是官方已经实现的常见的5个TaskExecuter。Spring 宣称对于任何场景,这些TaskExecuter完全够用了:

线程特点
SimpleAsyncTaskExecutor每次请求新开线程,没有最大线程数设置.不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程
SyncTaskExecutor不是异步的线程。同步可以用SyncTaskExecutor,但这个可以说不算一个线程池,因为还在原线程执行。这个类没有实现异步调用,只是一个同步操作。
ConcurrentTaskExecutorExecutor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类。
SimpleThreadPoolTaskExecutor是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类。
ThreadPoolTaskExecutor最常使用,推荐,是阿里巴巴Java开发规范中指定的线程类,要求jdk版本大于等于5。其实质是对java.util.concurrent.ThreadPoolExecutor 的包装。

       参考阿里巴巴java开发规范, 在线程池应用中:线程池不允许使用Executors去创建,也不允许使用系统默认的线程池,推荐通过 ThreadPoolExecutor 的方式,这样的处理方式让开发的工程师更加明确线程池的运行规则,规避资源耗尽的风险。

二、SpringBoot中使用@Async

        使用异步线程调用方法,过程如下:

  • 编写配置类,定义线程池
  • 启动类/配置文件上加上注解:@EnableAsync
  • 方法上加上注解:@Async

        下面演示案例中,我本地项目的目录,仅供参考:

2.1 启用@Async

        关键注解 @EnableAsync !!!可以加载启动类上,也可以加在配置文件上,效果是一样的。

  •  方式一:基于Springboot启动类启用
@EnableAsync
@SpringBootApplication
public class AsyncApplication {

    public static void main(String[] args) {
        SpringApplication.run(AsyncApplication.class, args);
    }

}
  • 方式二:基于Java配置的启用
// com.example.async.service 为即将开启异步线程业务的包位置(后面有详细讲解)
@EnableAsync
@Configuration
@ComponentScan("com.example.async.service")
public class AsyncConfiguration implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        return executor();
    }
    ...
}

2.2 @Async与线程池

        Spring应用默认的线程池,指在@Async注解在使用时,不指定线程池的名称。查看源码,@Async的默认线程池为SimpleAsyncTaskExecutor

@Slf4j
@Service
public class BusinessServiceImpl implements BusinessService {

    /**
     * 方法4:没有指定线程池,验证默认线程池也ok(不推荐:规避资源耗尽的风险)
     */
    @Async
    public void asyncDemo4() {
        log.info("asyncDemo4:" + Thread.currentThread().getName() + " 正在执行 ----------");
        try {
            Thread.sleep(2*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("asyncDemo4:" + Thread.currentThread().getName() + " 执行结束!!");
    }
}

2.3 @Async自定义线程池

        自定义线程池,可对系统中线程池更加细粒度的控制,方便调整线程池大小配置,线程执行异常控制和处理。在设置系统自定义线程池代替默认线程池时,虽可通过多种模式设置,但替换默认线程池最终产生的线程池有且只能设置一个(不能设置多个类继承AsyncConfigurer)。

        自定义线程池有如下模式:

  1. 重新实现接口AsyncConfigurer;
  2. 继承AsyncConfigurerSupport;
  3. 配置由自定义的TaskExecutor替代内置的任务执行器;

        三者使用方式大体相同,下面的案例将展示说明其一:实现接口AsyncConfigurer接口的方式。

  • 配置一个线程池 ThreadPoolTaskExecutor
/**
 * com.example.async.service:即将开启异步线程的业务方法是哪个
 *
 * 解释:
 *  1.即将开启异步线程业务的包位置:com.example.async.service
 *  2.通过 ThreadPoolExecutor 的方式,规避资源耗尽的风险
 */
@EnableAsync
@Configuration
@ComponentScan("com.example.async.service")
public class AsyncConfiguration implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        return executor();
    }

    /**
     * 执行需要依赖线程池,这里就来配置一个线程池
     * 1.当池子大小小于corePoolSize,就新建线程,并处理请求
     * 2.当池子大小等于corePoolSize,把请求放入workQueue(QueueCapacity)中,池子里的空闲线程就去workQueue中取任务并处理
     * 3.当workQueue放不下任务时,就新建线程入池,并处理请求,如果池子大小撑到了maximumPoolSize,就用RejectedExecutionHandler来做拒绝处理
     * 4.当池子的线程数大于corePoolSize时,多余的线程会等待keepAliveTime长时间,如果无请求可处理就自行销毁
     */
    @Bean("asyncExecutor")
    public ThreadPoolTaskExecutor executor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //设置线程名
        executor.setThreadNamePrefix("async-method-execute-");
        //设置核心线程数
        executor.setCorePoolSize(10);
        //设置最大线程数
        executor.setMaxPoolSize(50);
        //线程池所使用的缓冲队列
        executor.setQueueCapacity(100);
        //设置多余线程等待的时间,单位:秒
        executor.setKeepAliveSeconds(10);
        // 初始化线程
        executor.initialize();
        return executor;
    }
}
  • 执行异步线程方法,指定线程池:value 要与配置类 Bean() 中的name相同 
/**
 * 异步线程 - 执行业务
 * 注意:
 *  1.@Async 注解调用用线程池,不指定的话默认:SimpleAsyncTaskExecutor
 *  2.SimpleAsyncTaskExecutor 不是真的线程池,这个类不重用线程,默认每次调用都会创建一个新的线程
 */
@Slf4j
@Service
public class AsyncServiceImpl implements AsyncService {
    /**
     * 方法1:@Async 标注为异步任务:执行此方法的时候,会单独开启线程来执行,不影响主线程的执行
     */
    @Async("asyncExecutor")
    public void asyncDemo1() {
        log.info("asyncDemo1:" + Thread.currentThread().getName() + " 正在执行 ----------");
        // 故意等10秒,那么异步线程开起来,这样明显看到:方法2不用等方法1执行完就调用了
        try {
            Thread.sleep(10*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("asyncDemo1:" + Thread.currentThread().getName() + " 执行结束!!");
    }

    /**
     * 方法2:与方法1一起执行,证明2个线程异步执行,互不干扰
     */
    @Async("asyncExecutor")
    public void asyncDemo2() {
        log.info("asyncDemo2:" + Thread.currentThread().getName() + " 正在执行 ----------");
        try {
            Thread.sleep(5*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("asyncDemo2:" + Thread.currentThread().getName() + " 执行结束!!");
    }

    /**
     * 方法3:没有指定线程池,验证默认线程池也ok(不推荐:规避资源耗尽的风险)
     */
    @Async
    public void asyncDemo3() {
        log.info("asyncDemo3:" + Thread.currentThread().getName() + " 正在执行 ----------");
        try {
            Thread.sleep(1*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("asyncDemo3:" + Thread.currentThread().getName() + " 执行结束!!");
    }
}

2.4 启动测试

        通过 AsyncApplication 启动 SpringBoot 项目,Postman 进行接口测试:

http://127.0.0.1:8080/async/demo

  • 我写了4个demo,分别模拟4种情况,详情在注释里有写。
@Slf4j
@RestController
@RequestMapping("/async")
public class AsyncControllor {

    @Autowired
    private AsyncService asyncMethodService;
    @Autowired
    private BusinessService businessService;

    @GetMapping("/demo")
    public String demo()  {
        log.info("接口调用:【开始】 --------------------");
        try {
            // 执行异步任务 - 自定义线程池
            asyncMethodService.asyncDemo1();
            asyncMethodService.asyncDemo2();
            asyncMethodService.asyncDemo3();
            // 执行异步任务 - 默认线程池
            businessService.asyncDemo4();
        } catch (Exception e) {
            return "Exception";
        }
        log.info("接口调用:【结束】 --------------------");
        return "success";
    }
}
  • 运行结果:接口执行结束,异步线程仍在运行


总结

  1. @EnableAsync 是启动 @Async 异步线程的关键,可以加载启动类上,也可以加在配置文件上;
  2. 为了规避资源耗尽的风险,推荐通过 ThreadPoolExecutor 的方式创建线程池;
  3. @Async 注解标注在方法上,以便异步地调用该方法;

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

电脑端(PC)按键精灵——5.找色/找图命令

电脑端(PC)按键精灵——5.找色/找图命令 注:说了键盘、鼠标、其他、控制命令还有安装内容,现在说下颜色/图形命令,这一节相当重要 按键精灵小白入门详细教程: 电脑端(PC)按键精灵—小白入门 详细教程 命令介绍 1.GetPixelCol…

【C++类】

目录 前言一、类的定义二、类的访问限定符及封装2.1访问限定符2.2封装 三、类的大小3.1为什么需要内存对齐3.2为什么成员函数不占用类的内存?3.3为什么空类的大小是1个字节? 四、this指针4.1this指针的引入4.2this指针的特性 五、类的6个默认成员函数5.1…

飞书接入ChatGPT - 将ChatGPT集成到飞书机器人,直接拉满效率 【飞书ChatGPT机器人】

文章目录 前言环境列表视频教程1.飞书设置2.克隆feishu-chatgpt项目3.配置config.yaml文件4.运行feishu-chatgpt项目5.安装cpolar内网穿透6.固定公网地址7.机器人权限配置8.创建版本9.创建测试企业10. 机器人测试 前言 在飞书中创建chatGPT机器人并且对话,在下面操作步骤中,使…

6.4 一阶方程组与高阶方程的数值解法

学习目标: 学习一阶方程组与高阶方程的数值解法的目标可以分为以下几个方面: 掌握一阶方程组和高阶方程的基本概念和求解方法;理解数值解法的概念和原理,了解常见的数值解法;掌握欧拉方法、改进欧拉方法和龙格-库塔方…

深入探讨Linux驱动开发:Linux设备树

文章目录 一、设备树介绍二、设备树框架1.设备树框架2.节点基本格式3.节点部分属性简介 总结 一、设备树介绍 设备树(Device Tree,简称 DT)是一种在嵌入式系统中描述硬件设备的一种数据结构和编程语言。它用于将硬件设备的配置信息以树形结构…

Springboot 中快速完成文件上传,整合多平台神器

哈喽,大家好~ 又是做好人好事的一天,有个小可爱私下问我有没有好用的springboot文件上传工具,这不巧了嘛,正好我私藏了一个好东西,顺便给小伙伴们也分享一下,demo地址放在文末了。 文件上传在平常不过的一…

最新,有8本SCIE期刊被剔除,4月SCIESSCI期刊目录更新(附最新目录下载)

2023年4月18日,科睿唯安更新了WOS期刊目录,继上次3月WOS期刊目录更新大变动之后,此次4月更新又有8本SCIE期刊发生变动,其中有4本期刊被剔出SCIE数据库,4本期刊更改了名称和ISSN号。更新后的最新SCIE期刊目录共有9505本…

Flask 与 Django 先学哪个呢

本文把 Flask 和 Django 做一个比对,因为我对这两个 Python Web 框架都有实际的开发经验。希望我可以帮助您选择学习哪个框架,因为学习一个框架可能会非常耗时 —— 当然也很有趣! 相似之处 让我们从相似之处开始。 No. 1 Flask 和 Djang…

【ctfshow】命令执行->web29-web44

前言 半夜网抑云听歌听emo了 z 刷会儿题不然睡不着了呜呜呜 红中(hong_zh0) CSDN内容合伙人、2023年新星计划web安全方向导师、 华为MindSpore截至目前最年轻的优秀开发者、IK&N战队队长、 吉林师范大学网安大一的一名普通学生、搞网安论文拿了回大挑校二、 阿里云专家博…

数据结构复习题(包含答案)

第一章 概论 一、选择题 1、研究数据结构就是研究( D )。 A. 数据的逻辑结构 B. 数据的存储结构 C. 数据的逻辑结构和存储结构 D. 数据的逻辑结构、存储结构及其基本操作 2、算法分析的两个主要方面是( A …

【小技巧】word文档编辑技巧(一)

文章目录 一、显示显示导航显示所有字符 二、格式格式-三级目录格式-文本格式-图格式-表格式-公式格式-参考文献 三、小技巧交叉引用连续交叉引用表/图目录等自动更新分节符设置页眉/页码word转pdf带导航 一、显示 显示导航 开启导航:视图->显示框->导航窗格…

【Python从入门到进阶】16、文件的打开和关闭

接上篇《15、函数的定义和使用》 上一篇我们学习了Python中函数的定义和使用,包括函数的参数、返回值、局部变量和全景变量等操作。从本篇开始我们来开始学习Python对文件的一些操作,本篇我们主要讲解如何使用Python打开和关闭文件。 一、打开/创建文件…

【SVN】windows SVN安装使用教程(服务器4.3.4版本/客户端1.11.0版本)

介绍 这里是小编成长之路的历程,也是小编的学习之路。希望和各位大佬们一起成长! 以下为小编最喜欢的两句话: 要有最朴素的生活和最遥远的梦想,即使明天天寒地冻,山高水远,路远马亡。 一个人为什么要努力&a…

【设计模式】Java 的三种代理模式

文章目录 一、前言二、正文1、静态代理2、动态代理3、Cglib代理Spring中AOP使用代理 三、总结 一、前言 代理(Proxy)模式是一种结构型设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象。 这样做的好处是:可以在目标对…

activeMQ持久化报错的问题

activeMQ持久化,启动activeMQ报错, INFO | Using Persistence Adapter: JDBCPersistenceAdapter(org.apache.commons.dbcp2.BasicDataSource5148e82a) jvm 1 | WARN | Could not get JDBC connection: Cannot create PoolableConnectionFactory (Commun…

前端学习:HTML头部、布局

目录 HTML头部 一、HTML 元素 二、head标签和header标签的不同 三、HTML 元素 四、HTML 元素 五、HTML 元素 六、 HTML 七、HTML元素 为搜索引擎定义关键词: 为网页定义描述内容: 每60秒刷新当前页面: 八、HTML 九、HTML头部元素…

【故障检测】基于 KPCA 的故障检测【T2 和 Q 统计指数的可视化】(Matlab代码实现)

💥 💥 💞 💞 欢迎来到本博客 ❤️ ❤️ 💥 💥 🏆 博主优势: 🌞 🌞 🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 …

AI语音生成器是下一大安全威胁吗?

ChatGPT一经上市,有关监管人工智能的讨论就开始升温。任何试图遏制这种技术的做法都可能需要国际合作,需要我们在过去几十年来从未见过的合作程度,因此不太可能遏制人工智能。 人工智能是一项功能强大的技术,有望彻底改变我们生活…

Domino自带的JSON校验工具

大家好,才是真的好。 JSON数据在Notes/Domino已经变得非常重要。从Domino 10开始,在LotusScript语言中就加入了对JSON数据处理功能。在管理中,我们知道,从Domino 12版本开始就支持Domino自动化配置,也是使用JSON数据作…

C++ 简介

C 完全支持面向对象的程序设计,包括面向对象开发的四大特性: 封装(Encapsulation):封装是将数据和方法组合在一起,对外部隐藏实现细节,只公开对外提供的接口。这样可以提高安全性、可靠性和灵活…