SpringBoot使用@Async实现异步调用
1、@EnableAsync
首先,我们需要在启动类上添加 @EnableAsync 注解来声明开启异步方法。
@SpringBootApplication
@EnableAsync
public class SpringbootAsyncApplication { public static void main(String[] args) {
SpringApplication.run(SpringbootAsyncApplication.class, args);
} }
2、@Async
需要注意的,@Async在使用上有一些限制:
- 它只能应用于public修饰的方法
- 自调用–从同一个类中调用async方法,将不起作用
原因很简单:
- 只有公共方法,才可以被代理。
- 自调用不起作用,因为它越过了代理直接调用了方法。
2.1、无返回值的异步方法
这是一个异步运行的无返回值方法:
@Async
public void asyncMethodWithVoidReturnType() {
System.out.println("异步无返回值方法 "
+ Thread.currentThread().getName());
}
实例:
- AsyncTask:异步式任务类,定义了三个异步式方法。
@Component
public class AsyncTask {
Logger log= LoggerFactory.getLogger(AsyncTask.class); private Random random = new Random(); /**
* 定义三个异步式方法
* @throws InterruptedException
*/
@Async
public void taskOne() throws InterruptedException {
long start = System.currentTimeMillis();
//随机休眠若干毫秒
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
log.info("任务一执行完成耗时{}秒", (end - start)/1000f);
} @Async
public void taskTwo() throws InterruptedException {
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
log.info("任务二执行完成耗时{}秒", (end - start)/1000f);
} @Async
public void taskThree() throws InterruptedException {
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
log.info("任务三执行完成耗时{}秒", (end - start)/1000f);
} }
- 在测试类中调用三个异步式方法:
@SpringBootTest
@RunWith(SpringRunner.class)
public class AsyncTaskTest { @Autowired
private AsyncTask asyncTask; Logger log= LoggerFactory.getLogger(AsyncTaskTest.class); @Test
public void doAsyncTasks(){
try {
long start = System.currentTimeMillis();
//调用三个异步式方法
asyncTask.taskOne();
asyncTask.taskTwo();
asyncTask.taskThree();
Thread.sleep(5000);
long end = System.currentTimeMillis();
log.info("主程序执行完成耗时{}秒", (end - start)/1000f);
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
运行结果:可以看到三个方法没有顺序执行,这个复执行单元测试,您可能会遇到各种不同的结果,比如:
- 没有任何任务相关的输出
- 乱序的任务相关的输出
- 有部分任务相关的输出
原因是目前doTaskOne、doTaskTwo、doTaskThree三个函数的时候已经是异步执行了。主程序在异步调用之后,主程序并不会理会这三个函数是否执行完成了,由于没有其他需要执行的内容,所以程序就自动结束了,导致了不完整或是没有输出任务相关内容的情况。
2.1、有返回值的异步方法
@Async也可以应用有返回值的方法–通过在Future中包装实际的返回值:
/**
* 有返回值的异步方法
* @return
*/
@Async
public Future<String> asyncMethodWithReturnType() {
System.out.println("执行有返回值的异步方法 "
+ Thread.currentThread().getName());
try {
Thread.sleep(5000);
return new AsyncResult<String>("hello world !!!!");
} catch (InterruptedException e) {
//
}
return null;
}
Spring还提供了一个实现Future的AsyncResult类。这个类可用于跟踪异步方法执行的结果。
实例:
- 我们将2.1的实例改造成有返回值的异步方法:
@Async
public Future<String> taskOne() throws InterruptedException {
long start = System.currentTimeMillis();
//随机休眠若干毫秒
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
log.info("任务一执行完成耗时{}秒", (end - start)/1000f);
return new AsyncResult<>("任务一完事了");
}
taskTwo、taskThree方法做同样的改造。
- 测试有返回值的异步方法:
@Test
public void doFutureTask(){
try {
long start=System.currentTimeMillis();
Future<String> future1=asyncTask.taskOne();
Future <String> future2 = asyncTask.taskTwo();
Future <String> future3 = asyncTask.taskThree();
//三个任务执行完再执行主程序
do {
Thread.sleep(100);
} while (future1.isDone() && future2.isDone() && future3.isDone());
log.info("获取异步方法的返回值:{}", future1.get());
Thread.sleep(5000);
long end = System.currentTimeMillis();
log.info("主程序执行完成耗时{}秒", (end - start)/1000f);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
运行结果:可以看到三个任务完成后才执行主程序,还输出了异步方法的返回值。
3、 Executor
默认情况下,Spring使用SimpleAsyncTaskExecutor异步运行这些方法。
可以在两个级别上重写默认线程池——应用程序级别或方法级别。
3.1、方法级别重写Executor
所需的执行程序需要在配置类中声明 Executor:
@Configuration
@EnableAsync
public class SpringAsyncConfig { @Bean(name = "threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
return new ThreadPoolTaskExecutor();
}
}
然后,在@Async中的属性提供Executor名称:
@Async("threadPoolTaskExecutor")
public void asyncMethodWithConfiguredExecutor() {
System.out.println("Execute method with configured executor - "
+ Thread.currentThread().getName());
}
3.2、应用级别重写Executor
配置类应实现AsyncConfigurer接口,重写getAsyncExecutor()方法。
在这里,我们将返回整个应用程序的Executor,这样一来,它就成为运行以@Async注释的方法的默认Executor:
@Configuration
@EnableAsync
public class SpringApplicationAsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
return new ThreadPoolTaskExecutor();
}
}
3.3、自定义线程池配置
在上面,自定义线程池只是简单地返回了一个线程池:
return new ThreadPoolTaskExecutor();
实际上,还可以对线程池做一些配置:
@Configuration
@EnableAsync
public class SpringPropertiesAsyncConfig implements AsyncConfigurer { /**
* 对线程池进行配置
* @return
*/
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(20);
taskExecutor.setMaxPoolSize(200);
taskExecutor.setQueueCapacity(25);
taskExecutor.setKeepAliveSeconds(200);
taskExecutor.setThreadNamePrefix("oKong-");
// 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.initialize();
return taskExecutor;
}
}
ThreadPoolTaskExecutor配置参数的简单说明:
corePoolSize:线程池维护线程的最少数量
keepAliveSeconds:允许的空闲时间,当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
maxPoolSize:线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
queueCapacity:缓存队列
rejectedExecutionHandler:线程池对拒绝任务(无线程可用)的处理策略。这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务。还有一个是AbortPolicy策略:处理程序遭到拒绝将抛出运行时RejectedExecutionException。
4、异常处理
当方法返回类型为Future时,异常处理很容易– Future.get()方法将抛出异常。
但是如果是无返回值的异步方法,异常不会传播到调用线程。因此,我们需要添加额外的配置来处理异常。
我们将通过实现AsyncUncaughtExceptionHandler接口来创建自定义异步异常处理程序。
当存在任何未捕获的异步异常时,将调用handleUncaughtException()方法:
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
System.out.println("Exception message - " + throwable.getMessage());
System.out.println("Method name - " + method.getName());
for (Object param : objects) {
System.out.println("Parameter value - " + param);
}
}
}
上面,我们使用配置类实现了AsyncConfigurer接口。
作为其中的一部分,我们还需要重写getAsyncUncaughtExceptionHandler()方法以返回我们的自定义异步异常处理:
/**
* 返回自定义异常处理
* @return
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new CustomAsyncExceptionHandler();
}
5、总结
这里异步请求的使用及相关配置,如超时,异常等处理。在剥离一些和业务无关的操作时,就可以考虑使用异步调用进行其他无关业务操作,以此提供业务的处理效率。或者一些业务场景下可拆分出多个方法进行同步执行又互不影响时,也可以考虑使用异步调用方式提供执行效率。
1、@EnableAsync
首先,我们需要在启动类上添加 @EnableAsync 注解来声明开启异步方法。
@SpringBootApplication
@EnableAsync
public class SpringbootAsyncApplication { public static void main(String[] args) {
SpringApplication.run(SpringbootAsyncApplication.class, args);
} }
2、@Async
需要注意的,@Async在使用上有一些限制:
- 它只能应用于public修饰的方法
- 自调用–从同一个类中调用async方法,将不起作用
原因很简单:
- 只有公共方法,才可以被代理。
- 自调用不起作用,因为它越过了代理直接调用了方法。
2.1、无返回值的异步方法
这是一个异步运行的无返回值方法:
@Async
public void asyncMethodWithVoidReturnType() {
System.out.println("异步无返回值方法 "
+ Thread.currentThread().getName());
}
实例:
- AsyncTask:异步式任务类,定义了三个异步式方法。
@Component
public class AsyncTask {
Logger log= LoggerFactory.getLogger(AsyncTask.class); private Random random = new Random(); /**
* 定义三个异步式方法
* @throws InterruptedException
*/
@Async
public void taskOne() throws InterruptedException {
long start = System.currentTimeMillis();
//随机休眠若干毫秒
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
log.info("任务一执行完成耗时{}秒", (end - start)/1000f);
} @Async
public void taskTwo() throws InterruptedException {
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
log.info("任务二执行完成耗时{}秒", (end - start)/1000f);
} @Async
public void taskThree() throws InterruptedException {
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
log.info("任务三执行完成耗时{}秒", (end - start)/1000f);
} }
- 在测试类中调用三个异步式方法:
@SpringBootTest
@RunWith(SpringRunner.class)
public class AsyncTaskTest { @Autowired
private AsyncTask asyncTask; Logger log= LoggerFactory.getLogger(AsyncTaskTest.class); @Test
public void doAsyncTasks(){
try {
long start = System.currentTimeMillis();
//调用三个异步式方法
asyncTask.taskOne();
asyncTask.taskTwo();
asyncTask.taskThree();
Thread.sleep(5000);
long end = System.currentTimeMillis();
log.info("主程序执行完成耗时{}秒", (end - start)/1000f);
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
运行结果:可以看到三个方法没有顺序执行,这个复执行单元测试,您可能会遇到各种不同的结果,比如:
- 没有任何任务相关的输出
- 乱序的任务相关的输出
- 有部分任务相关的输出
原因是目前doTaskOne、doTaskTwo、doTaskThree三个函数的时候已经是异步执行了。主程序在异步调用之后,主程序并不会理会这三个函数是否执行完成了,由于没有其他需要执行的内容,所以程序就自动结束了,导致了不完整或是没有输出任务相关内容的情况。
2.1、有返回值的异步方法
@Async也可以应用有返回值的方法–通过在Future中包装实际的返回值:
/**
* 有返回值的异步方法
* @return
*/
@Async
public Future<String> asyncMethodWithReturnType() {
System.out.println("执行有返回值的异步方法 "
+ Thread.currentThread().getName());
try {
Thread.sleep(5000);
return new AsyncResult<String>("hello world !!!!");
} catch (InterruptedException e) {
//
}
return null;
}
Spring还提供了一个实现Future的AsyncResult类。这个类可用于跟踪异步方法执行的结果。
实例:
- 我们将2.1的实例改造成有返回值的异步方法:
@Async
public Future<String> taskOne() throws InterruptedException {
long start = System.currentTimeMillis();
//随机休眠若干毫秒
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
log.info("任务一执行完成耗时{}秒", (end - start)/1000f);
return new AsyncResult<>("任务一完事了");
}
taskTwo、taskThree方法做同样的改造。
- 测试有返回值的异步方法:
@Test
public void doFutureTask(){
try {
long start=System.currentTimeMillis();
Future<String> future1=asyncTask.taskOne();
Future <String> future2 = asyncTask.taskTwo();
Future <String> future3 = asyncTask.taskThree();
//三个任务执行完再执行主程序
do {
Thread.sleep(100);
} while (future1.isDone() && future2.isDone() && future3.isDone());
log.info("获取异步方法的返回值:{}", future1.get());
Thread.sleep(5000);
long end = System.currentTimeMillis();
log.info("主程序执行完成耗时{}秒", (end - start)/1000f);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
@Test
public void doFutureTask(){
try {
long start=System.currentTimeMillis();
Future<String> future1=asyncTask.taskOne();
Future <String> future2 = asyncTask.taskTwo();
Future <String> future3 = asyncTask.taskThree();
//三个任务执行完再执行主程序
do {
Thread.sleep(100);
} while (future1.isDone() && future2.isDone() && future3.isDone());
log.info("获取异步方法的返回值:{}", future1.get());
Thread.sleep(5000);
long end = System.currentTimeMillis();
log.info("主程序执行完成耗时{}秒", (end - start)/1000f);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
运行结果:可以看到三个任务完成后才执行主程序,还输出了异步方法的返回值。
在这里插入图片描述
3、 Executor
默认情况下,Spring使用SimpleAsyncTaskExecutor异步运行这些方法。
可以在两个级别上重写默认线程池——应用程序级别或方法级别。
3.1、方法级别重写Executor
所需的执行程序需要在配置类中声明 Executor:
@Configuration
@EnableAsync
public class SpringAsyncConfig { @Bean(name = "threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
return new ThreadPoolTaskExecutor();
}
}
然后,在@Async中的属性提供Executor名称:
@Async("threadPoolTaskExecutor")
public void asyncMethodWithConfiguredExecutor() {
System.out.println("Execute method with configured executor - "
+ Thread.currentThread().getName());
}
3.2、应用级别重写Executor
配置类应实现AsyncConfigurer接口,重写getAsyncExecutor()方法。
在这里,我们将返回整个应用程序的Executor,这样一来,它就成为运行以@Async注释的方法的默认Executor:
@Configuration
@EnableAsync
public class SpringApplicationAsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
return new ThreadPoolTaskExecutor();
}
}
3.3、自定义线程池配置
在上面,自定义线程池只是简单地返回了一个线程池:
return new ThreadPoolTaskExecutor();
实际上,还可以对线程池做一些配置:
@Configuration
@EnableAsync
public class SpringPropertiesAsyncConfig implements AsyncConfigurer { /**
* 对线程池进行配置
* @return
*/
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(20);
taskExecutor.setMaxPoolSize(200);
taskExecutor.setQueueCapacity(25);
taskExecutor.setKeepAliveSeconds(200);
taskExecutor.setThreadNamePrefix("oKong-");
// 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.initialize();
return taskExecutor;
}
}
ThreadPoolTaskExecutor配置参数的简单说明:
corePoolSize:线程池维护线程的最少数量
keepAliveSeconds:允许的空闲时间,当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
maxPoolSize:线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
queueCapacity:缓存队列
rejectedExecutionHandler:线程池对拒绝任务(无线程可用)的处理策略。这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务。还有一个是AbortPolicy策略:处理程序遭到拒绝将抛出运行时RejectedExecutionException。
4、异常处理
当方法返回类型为Future时,异常处理很容易– Future.get()方法将抛出异常。
但是如果是无返回值的异步方法,异常不会传播到调用线程。因此,我们需要添加额外的配置来处理异常。
我们将通过实现AsyncUncaughtExceptionHandler接口来创建自定义异步异常处理程序。
当存在任何未捕获的异步异常时,将调用handleUncaughtException()方法:
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
System.out.println("Exception message - " + throwable.getMessage());
System.out.println("Method name - " + method.getName());
for (Object param : objects) {
System.out.println("Parameter value - " + param);
}
}
}
上面,我们使用配置类实现了AsyncConfigurer接口。
作为其中的一部分,我们还需要重写getAsyncUncaughtExceptionHandler()方法以返回我们的自定义异步异常处理:
/**
* 返回自定义异常处理
* @return
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new CustomAsyncExceptionHandler();
}
5、总结
这里异步请求的使用及相关配置,如超时,异常等处理。在剥离一些和业务无关的操作时,就可以考虑使用异步调用进行其他无关业务操作,以此提供业务的处理效率。或者一些业务场景下可拆分出多个方法进行同步执行又互不影响时,也可以考虑使用异步调用方式提供执行效率。
SpringBoot使用@Async实现异步调用的更多相关文章
- spring boot中使用@Async实现异步调用任务
本篇文章主要介绍了spring boot中使用@Async实现异步调用任务,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 什么是“异步调用”? “异步调用”对应的是“同步 ...
- spring boot 学习(十一)使用@Async实现异步调用
使用@Async实现异步调用 什么是”异步调用”与”同步调用” “同步调用”就是程序按照一定的顺序依次执行,,每一行程序代码必须等上一行代码执行完毕才能执行:”异步调用”则是只要上一行代码执行,无需等 ...
- Spring Boot使用@Async实现异步调用
原文:http://blog.csdn.net/a286352250/article/details/53157822 项目GitHub地址 : https://github.com/FrameRes ...
- Spring Boot使用@Async实现异步调用:自定义线程池
前面的章节中,我们介绍了使用@Async注解来实现异步调用,但是,对于这些异步执行的控制是我们保障自身应用健康的基本技能.本文我们就来学习一下,如果通过自定义线程池的方式来控制异步调用的并发. 定义线 ...
- 56. spring boot中使用@Async实现异步调用【从零开始学Spring Boot】
什么是"异步调用"? "异步调用"对应的是"同步调用",同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执 ...
- 注解@Async解决异步调用问题
序言:Spring中@Async 根据Spring的文档说明,默认采用的是单线程的模式的.所以在Java应用中,绝大多数情况下都是通过同步的方式来实现交互处理的. 那么当多个任务的执行势必会相互影响. ...
- Spring @Async实现异步调用示例
什么是“异步调用”? “异步调用”对应的是“同步调用”,同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行:异步调用指程序在顺序执行时,不等待异步调用的语句返回结果 ...
- Spring Boot中使用@Async实现异步调用
在Spring Boot中,我们只需要通过使用@Async注解就能简单的将原来的同步函数变为异步函数,为了让@Async注解能够生效,还需要在Spring Boot的主程序中配置@EnableAsyn ...
- Spring Boot2.0之@Async实现异步调用
补充一个知识点: lombok底层原理使用的是: 字节码技术ASM修改字节码文件,生成比如类似于get() set( )方法 一定要在开发工具安装 在编译时候修改字节码文件(底层使用字节码技术),线上 ...
随机推荐
- 洛谷 P3711 - 仓鼠的数学题(多项式)
洛谷题面传送门 提供一种不太一样的做法. 假设要求的多项式为 \(f(x)\).我们考察 \(f(x)-f(x-1)\),不难发现其等于 \(\sum\limits_{i=0}^na_ix^i\) 考 ...
- Codeforces 1175F - The Number of Subpermutations(线段树+单调栈+双针/分治+启发式优化)
Codeforces 题面传送门 & 洛谷题面传送门 由于这场的 G 是道毒瘤题,蒟蒻切不动就只好来把这场的 F 水掉了 看到这样的设问没人想到这道题吗?那我就来发篇线段树+单调栈的做法. 首 ...
- SQLite3 约束和简单命令
安装sqlite3,配置环境变量. 1.打开数据库 sqlite3.exe db_name.db 2.常用命令 .tables 查看所有表 .headers on 设置显示表头 .schema tab ...
- 【GS模型】使用R包sommer进行基因组选择的GBLUP和RRBLUP分析?
目录 简介 GS示例代码 简介 R包sommer内置了C++,运算速度还是比较快的,功能也很丰富,可求解各种复杂模型.语法相比于lme4包也要好懂一些. 建议查看文档:vignette("v ...
- 联盛德 HLK-W806 (六): I2C驱动SSD1306 128x64 OLED液晶屏
目录 联盛德 HLK-W806 (一): Ubuntu20.04下的开发环境配置, 编译和烧录说明 联盛德 HLK-W806 (二): Win10下的开发环境配置, 编译和烧录说明 联盛德 HLK-W ...
- Idea中JSP页面中out内置对象报错out.println标红问题
问题如图: 解决方法: 导入jar包 1.在pom.xml的<dependencies>里面复制 <dependency> <groupId>javax.servl ...
- abundant
In ecology [生态学], local abundance is the relative representation of a species in a particular ecosys ...
- 大数据学习day21-----spark04------1. 广播变量 2. RDD中的cache 3.RDD的checkpoint方法 4. 计算学科最受欢迎老师TopN
1. 广播变量 1.1 补充知识(来源:https://blog.csdn.net/huashetianzu/article/details/7821674) 之所以存在reduce side jo ...
- 【STM8】添加头文件、加入库函数
下面顺便放上STM8L15x-16x-05x的固件库,以及固件库里没有的<stm8l15x_conf.h> 链接打开后,还会发现另外两个文件夹,<src><inc> ...
- Dubbo多注册中心
一.创建提供者08-provider-registers (1) 创建工程 直接复制05-provider-group工程,并命名为08-provider-registers (2) 修改配置文件 二 ...