Spring Boot中异步线程池@Async

很多业务场景需要使用异步去完成,比如:发送短信通知。要完成异步操作一般有两种:

1、消息队列MQ

2、线程池处理。

我们来看看Spring框架中如何去使用线程池来完成异步操作,以及分析背后的原理。

一. Spring异步线程池的接口类 :TaskExecutor

在Spring4中,Spring中引入了一个新的注解@Async,这个注解让我们在使用Spring完成异步操作变得非常方便。

Spring异步线程池的接口类,其实质是
java.util.concurrent.Executor

Spring 已经实现的异常线程池:

1. SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。
2. SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方
3. ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类
4. SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类
5. ThreadPoolTaskExecutor :最常使用,推荐。 其实质是对java.util.concurrent.ThreadPoolExecutor的包装,

二、简单使用说明

Spring中用@Async注解标记的方法,称为异步方法。在spring boot应用中使用@Async很简单:

1、调用异步方法类上或者启动类加上注解@EnableAsync

2、在需要被异步调用的方法外加上@Async

3、所使用的@Async注解方法的类对象应该是Spring容器管理的bean对象;

启动类加上注解@EnableAsync:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;@SpringBootApplication
@EnableAsync
public class CollectorApplication {public static void main(String[] args) throws Exception {SpringApplication.run(CollectorApplication.class, args);}
}

在需要被异步调用的方法外加上@Async,同时类AsyncService加上注解@Service或者@Component,使其对象成为Spring容器管理的bean对象;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;@Service
@Transactional
public class AsyncService {@Asyncpublic void asyncMethod(String s) {System.out.println("receive:" + s);}public void test() {System.out.println("test");asyncMethod();//同一个类里面调用异步方法}@Asyncpublic void test2() {AsyncService asyncService  = context.getBean(AsyncService.class);asyncService.asyncMethod();//异步}/*** 异布调用返回Future*/@Asyncpublic Future<String> asyncInvokeReturnFuture(int i) {System.out.println("asyncInvokeReturnFuture, parementer="+ i);Future<String> future;try {Thread.sleep(1000 * 1);future = new AsyncResult<String>("success:" + i);} catch (InterruptedException e) {future = new AsyncResult<String>("error");}return future;}
}//异步方法和普通的方法调用相同
asyncService.asyncMethod("123");
Future<String> future = asyncService.asyncInvokeReturnFuture(100);
System.out.println(future.get());

如果将一个类声明为异步类@Async,那么这个类对外暴露的方法全部成为异步方法。

@Async
@Service
public class AsyncClass {public AsyncClass() {System.out.println("----init AsyncClass----");}volatile int index = 0;public void foo() {System.out.println("asyncclass foo, index:" + index);}public void foo(int i) {this.index = i;System.out.println("asyncclass foo, index:" + i);}public void bar(int i) {this.index = i;System.out.println("asyncclass bar, index:" + i);}
}

这里需要注意的是:

1、同一个类里面调用异步方法不生效:原因默认类内的方法调用不会被aop拦截,即调用方和被调用方是在同一个类中,是无法产生切面的,该对象没有被Spring容器管理。即@Async方法不生效。

解决办法:如果要使同一个类中的方法之间调用也被拦截,需要使用spring容器中的实例对象,而不是使用默认的this,因为通过bean实例的调用才会被spring的aop拦截

本例使用方法:AsyncService asyncService = context.getBean(AsyncService.class); 然后使用这个引用调用本地的方法即可达到被拦截的目的

备注:这种方法只能拦截protected,default,public方法,private方法无法拦截。这个是spring aop的一个机制。

2、如果不自定义异步方法的线程池默认使用SimpleAsyncTaskExecutor。SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。并发大的时候会产生严重的性能问题。

3、异步方法返回类型只能有两种:void和
java.util.concurrent.Future。

1)当返回类型为void的时候,方法调用过程产生的异常不会抛到调用者层面,

可以通过注
AsyncUncaughtExceptionHandler来捕获此类异常

2)当返回类型为Future的时候,方法调用过程产生的异常会抛到调用者层面

三、定义通用线程池

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

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

通过查看 Spring 源码关于 @Async 的默认调用规则,会优先查询源码中实现 AsyncConfigurer 这个接口的类,实现这个接口的类为 AsyncConfigurerSupport。但默认配置的线程池和异步处理方法均为空,所以,无论是继承或者重新实现接口,都需指定一个线程池。且重新实现 public Executor getAsyncExecutor () 方法。

实现接口 AsyncConfigurer
@Configurationpublic class AsyncConfiguration implements AsyncConfigurer {@Bean("taskExecutor")public ThreadPoolTaskExecutor executor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();int corePoolSize = 10;executor.setCorePoolSize(corePoolSize);int maxPoolSize = 50;executor.setMaxPoolSize(maxPoolSize);int queueCapacity = 10;executor.setQueueCapacity(queueCapacity);executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.setThreadNamePrefix( "asyncServiceExecutor-");executor.setWaitForTasksToCompleteOnShutdown(true);executor.setAwaitTerminationSeconds(awaitTerminationSeconds);executor.initialize();return executor;}@Overridepublic Executor getAsyncExecutor() {return executor();}}
继承 AsyncConfigurerSupport
Configuration  
@EnableAsync  
class SpringAsyncConfigurer extends AsyncConfigurerSupport {  @Bean  public ThreadPoolTaskExecutor asyncExecutor() {  ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();  threadPool.setCorePoolSize(3);  threadPool.setMaxPoolSize(3);  threadPool.setWaitForTasksToCompleteOnShutdown(true);  threadPool.setAwaitTerminationSeconds(60 * 15);  return threadPool;  }  @Override  public Executor getAsyncExecutor() {  return asyncExecutor;  }  
}
配置自定义的 TaskExecutor (建议采用方式)

在Spring Boot主类中定义一个线程池,public Executor taskExecutor() 方法用于自定义自己的线程池,线程池前缀”taskExecutor-”。如果不定义,则使用系统默认的线程池。

@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}@EnableAsync@Configurationclass TaskPoolConfig {@Beanpublic Executor taskExecutor1() {ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();pool.setCorePoolSize(5); //线程池活跃的线程数pool.setMaxPoolSize(10); //线程池最大活跃的线程数pool.setWaitForTasksToCompleteOnShutdown(true);pool.setThreadNamePrefix("defaultExecutor");return pool;}@Bean("taskExecutor")public Executor taskExecutor2() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(10);executor.setMaxPoolSize(20);executor.setQueueCapacity(200);executor.setKeepAliveSeconds(60);executor.setThreadNamePrefix("taskExecutor-");executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.setWaitForTasksToCompleteOnShutdown(true);executor.setAwaitTerminationSeconds(60);return executor;}}
}

上面我们通过ThreadPoolTaskExecutor创建了一个线程池,同时设置了如下参数:

核心线程数10:线程池创建时初始化的线程数

最大线程数20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程

缓冲队列200:用来缓冲执行任务的队列

允许线程的空闲时间60秒:超过了核心线程数之外的线程,在空闲时间到达之后会被销毁

线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池

线程池对拒绝任务的处理策略:此处采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在execute方法的调用线程中运行被拒绝的任务;如果执行程序已被关闭,则会丢弃该任务

设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean

设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住

也可以单独类来配置线程池:

/*** 线程池参数配置,多个线程池实现线程池隔离,@Async注解,默认使用系统自定义线程池,可在项目中设置多个线程池,在异步调用的时候,指明需要调用的线程池名称,比如:@Async("taskName")**/
@EnableAsync
@Configuration
public class TaskPoolConfig {@Bean("taskExecutor")public Executor taskExecutor() {//返回可用处理器的Java虚拟机的数量 12int i = Runtime.getRuntime().availableProcessors();System.out.println("系统最大线程数  : " + i);ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//核心线程池大小executor.setCorePoolSize(16);//最大线程数executor.setMaxPoolSize(20);//配置队列容量,默认值为Integer.MAX_VALUEexecutor.setQueueCapacity(99999);//活跃时间executor.setKeepAliveSeconds(60);//线程名字前缀executor.setThreadNamePrefix("asyncServiceExecutor -");//设置此执行程序应该在关闭时阻止的最大秒数,以便在容器的其余部分继续关闭之前等待剩余的任务完成他们的执行executor.setAwaitTerminationSeconds(60);//等待所有的任务结束后再关闭线程池executor.setWaitForTasksToCompleteOnShutdown(true);return executor;}
}

四、异步方法使用线程池

@Async(“线程池名称”),指定value使用自己定义的线程池:

只需要在@Async注解中指定线程池名即可

@Component
public class Task {//默认使用线程池@Asyncpublic void doTaskOne() throws Exception {System.out.println("开始做任务");long start = System.currentTimeMillis();Thread.sleep(random.nextInt(10000));long end = System.currentTimeMillis();System.out.println("完成任务耗时:" + (end - start) + "毫秒");}//根据Bean Name指定特定线程池@Async("taskExecutor")public void doTaskOne() throws Exception {System.out.println("开始做任务");long start = System.currentTimeMillis();Thread.sleep(random.nextInt(10000));long end = System.currentTimeMillis();System.out.println("完成任务耗时:" + (end - start) + "毫秒");}
}

注意事项(一定注意)

在使用@Async注解时,很多小伙伴都会发现异步使用失败。主要原因是异步方法的定义出了问题。

1、异步方法不能使用static修饰

2、异步类没有使用@Component注解(或其他注解)导致spring无法扫描到异步类

3、异步方法和调用异步方法的方法不能在同一个类

4、类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象

5、如果使用SpringBoot框架必须在启动类中增加@EnableAsync注解​

 

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

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

相关文章

【激光SLAM】里程计运动模型及标定

目录 里程计模型两轮差分底盘的运动学模型优点差分模型 三轮全向底盘的运动学模型优点全向模型 航迹推算(Dead Reckoning) 里程计标定线性最小二乘的基本原理最小二乘的直线拟合最小二乘在里程计标定中的应用方法 里程计模型 里程计相关介绍 两轮差分底盘的运动学模型 优点 …

无向图-树的重心-DFS求解

思路&#xff1a; 本题的本质是树的dfs&#xff0c; 每次dfs可以确定以u为重心的最大连通块的节点数&#xff0c;并且更新一下ans。 也就是说&#xff0c;dfs并不直接返回答案&#xff0c;而是在每次更新中迭代一次答案。 这样的套路会经常用到&#xff0c;在 树的dfs 题目中…

2024年MacBook上实用软件

Mac软件-mac软件下载-mac软件大全-MacZMacz下载是一个专业的Mac苹果电脑软件下载网站&#xff0c;提供专业的Mac软件、Mac游戏、精品插件以及各类海量素材下载&#xff0c;mac下载网站有Mac平台上常用好用的软件&#xff0c;有时下热门好玩的Mac游戏&#xff0c;还有各类PS、AE…

js去掉数组中的undefined及空字符串、null

方法一、filter函数 var arr [原数组]var newArr arr.filter(item > item && item.trim())console.log(newArr)例子&#xff1a; 可以使用JavaScript的filter()函数来过滤掉数组中的undefined元素。 const arr [1, undefined, hello, null, true];const filter…

CentOS 7中搭建NFS文件共享服务器的完整步骤

CentOS 7中搭建NFS文件共享服务器的完整步骤 要求&#xff1a;实现镜像文件共享&#xff0c;并基于挂载的共享目录配置yum源。 系统环境&#xff1a; 服务器&#xff1a;172.20.26.167-CentOS7.6 客户端&#xff1a;172.20.26.198-CentOS7.6 1、在服务器和客户端上&#x…

从奥迪Quattro到碧然德:揭秘技术品牌成功打造与推广的秘诀

在当前全球化和信息化快速发展的背景下&#xff0c;技术品牌的打造不仅是企业竞争力提升的重要途径&#xff0c;也是企业实现长远发展的基石。通过深入剖析&#xff0c;我们认识到&#xff0c;技术品牌的建设并非一蹴而就的过程&#xff0c;而是需要企业准确把握市场趋势&#…

Spring Boot + flowable 快速实现工作流

背景 使用flowable自带的flowable-ui制作流程图 使用springboot开发流程使用的接口完成流程的业务功能 文章来源&#xff1a;https://blog.csdn.net/zhan107876/article/details/120815560 一、flowable-ui部署运行 flowable-6.6.0 运行 官方demo 参考文档&#xff1a; htt…

编程笔记 html5cssjs 071 JavaScript Symbol 数据类型

编程笔记 html5&css&js 071 JavaScript Symbol 数据类型 一、Symbol 数据类型二、 示例小结 在JavaScript中&#xff0c;Symbol 是一种基本数据类型&#xff0c;它于 ECMAScript 6&#xff08;ES6&#xff09;中引入&#xff0c;用来创建唯一的、不可变的值。这些值可以…

【QT+QGIS跨平台编译】之二十九:【HDF5+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

文章目录 一、HDF5介绍二、文件下载三、文件分析四、pro文件一、HDF5介绍 HDF5(层次数据格式第5版)是一种用于存储和组织大量数据的文件格式和技术集合。它由美国国家超级计算应用中心(NCSA)开发,旨在解决复杂数据的存储和分布问题。HDF5支持各种数据类型,能够有效地存储…

24种设计模式之行为型模式(下)-Java版

软件设计模式是前辈们代码设计经验的总结&#xff0c;可以反复使用。设计模式共分为3大类&#xff0c;创建者模式(6种)、结构型模式(7种)、行为型模式(11种)&#xff0c;一共24种设计模式&#xff0c;软件设计一般需要满足7大基本原则。下面通过5章的学习一起来看看设计模式的魅…

大端和小端模式介绍

介绍 “大端”和“小端”通常指的是字节序&#xff08;Byte Order&#xff09;的两种类型&#xff0c;也被称为端序&#xff08;Endianness&#xff09;。在多字节的数据类型&#xff08;如整数&#xff09;中&#xff0c;字节可以以不同的顺序存储&#xff0c;这影响了计算机…

风行智能电视G32Y 强制刷机升级方法,附刷机升级数据MstarUpgrade.bin

升级步骤&#xff1a; 1、下载刷机数据&#xff0c;如是压缩包&#xff0c;需要先解压&#xff0c;然后将刷机bin格式的文件重命名为MstarUpgrade.bin 2、将此文件放到U盘根目录 &#xff08;U盘格式FAT32&#xff0c;单分区&#xff0c;建议4G的优盘刷机成功率高&#xff09;…

JD京东商品详情数据获取API测试示例

item_get 获得JD商品详情item_search 按关键字搜索商品item_search_img 按图搜索京东商品&#xff08;拍立淘&#xff09;item_search_shop 获得店铺的所有商品item_history_price 获取商品历史价格信息item_recommend 获取推荐商品列表buyer_order_list 获取购买到的商品订单列…

彻底学会系列:一、机器学习之线性回归(一)

1.基本概念(basic concept) 线性回归&#xff1a; 有监督学习的一种算法。主要关注多个因变量和一个目标变量之间的关系。 因变量&#xff1a; 影响目标变量的因素&#xff1a; X 1 , X 2 . . . X_1, X_2... X1​,X2​... &#xff0c;连续值或离散值。 目标变量&#xff1a; …

JAVA毕业设计126—基于Java+Springboot+Vue的二手交易商城管理系统(源代码+数据库)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootVue的二手交易商城管理系统(源代码数据库)126 一、系统介绍 本项目前后端分离&#xff0c;本系统分为管理员、用户两种角色 1、用户&#xff1a; 注册、登录、…

docker更换镜像源

添加的镜像源 {"registry-mirrors": ["https://registry.cn-hangzhou.aliyuncs.com", "https://reg-mirror.qiniu.com/", "https://docker.mirrors.ustc.edu.cn"] }docker更换镜像源之后一定要重启守卫 systemctl daemon-reloaddock…

【Ubuntu】安装hbase

前提 需要安装java 安装 HBase 下载并解压 HBase 安装包&#xff1a; wget https://dlcdn.apache.org/hbase/2.5.7/hbase-2.5.7-bin.tar.gz tar -zxvf hbase-2.5.7-bin.tar.gz配置 HBase 环境变量&#xff1a; export HBASE_HOME/path/to/hbase-2.5.7 export PATH$PATH:$H…

ESP32QRCodeReader库使用,ESP32-CAM识别二维码并向自写接口发出请求确认身份。

#include <Arduino.h> #include <WiFi.h> #include <HTTPClient.h> #include <ESP32QRCodeReader.h>#define WIFI_SSID "username" #define WIFI_PASSWORD "password" // 连接电脑主机的IP地址的8088端口 #define WEBHOOK_URL &qu…

docker-学习-5

docker-学习第五天 docker-学习第五天1. 昨天的练习回顾1.1. 练习11.2. 练习2 2. 命令2.1. 看镜像的详细信息 3. Dockerfile指令3.1. 常见的指令3.2. ENTRYPOINT和CMD的区别3.3. RUN中的set指令 4. 镜像的原理4.1. 为什么 Docker 镜像要采用这种分层结构呢&#xff1f;4.2. doc…

JavaScript ATM取款机

①&#xff1a;循环的时候&#xff0c;需要反复提示输入框&#xff0c;所以提示框写到循环里面 ②&#xff1a;退出的条件是用户输入了 4&#xff0c;如果是4&#xff0c;则结束循环&#xff0c;不在弹窗 ③&#xff1a;提前准备一个金额预先存储一个数额 ④&#xff1a;取钱…