一些业务场景我们需要使用多线程异步执行任务,加快任务执行速度。

之前有写过一篇文章叫做: 异步编程利器:CompletableFuture

在实际工作中也更加推荐使用CompletableFuture,因为它实现异步方式更加优雅,而且功能更加强大!

既然SpringBoot能通过 @Async 也实现异步执行任务,那么这篇文章就来总结下如何使用 @Async 实现异步执行任务。

一、SpringBoot使用@Async注解步骤

1、启动类上使用@EnableAsync注解

@SpringBootApplication
@EnableAsync
public class Application { public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

2、异步方法所在的类注入容器中

 @Componet
public class Test{ }

除了@Componet,也可以是@Controller、@RestController、@Service、@Configuration等注解,加入到Ioc容器里。

3、方法上加上@Async注解

@Service
public class Test{ @Async
public void a() { }
}

二、哪些情况会导致@Async异步失效?

如果你明明是按照上面的步骤来的,但是发现@Async注解还是不起作用,这里还有两点注意,因为@Async是基于Aop思想实现的,所有下面两种情况也会失效。

1、异步方法使用static修饰

    @Async
public static void a() { }

2、调用方法和异步方法在同一个类中

当异步方法和调用方法在同一个类中时,是没办法通过Ioc里的bean来执行异步方法的,从而变成同步方法。

如下:

@Component
public class Task { /**
* 调异步方法和异步方法在同一个类 @Async执行失败
*/
public void dotask() {
this.taskOne();
this.taskTwo();
} @Async
public void taskOne() {
//执行任务1
} @Async
public void taskTwo() {
//执行任务2
}
}

三、SpringBoot结合@Async实现异步示例

首先我们来看同步方法

1、同步调用示例

@Component
@Slf4j
public class DemoTask { public void taskOne() throws Exception {
log.info("===执行任务1===");
long start = System.currentTimeMillis();
Thread.sleep(200);
long end = System.currentTimeMillis();
log.info("任务1执行结束,总耗时={} 毫秒", end - start);
} public void taskTwo() throws Exception {
log.info("===执行任务2===");
long start = System.currentTimeMillis();
Thread.sleep(200);
long end = System.currentTimeMillis();
log.info("任务2执行结束,总耗时={} 毫秒", end - start);
} public void taskThere() throws Exception {
log.info("===执行任务3===");
long start = System.currentTimeMillis();
Thread.sleep(200);
long end = System.currentTimeMillis();
log.info("任务3执行结束,总耗时={} 毫秒", end - start);
}
}

执行方法

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoTaskTest { @Autowired
private DemoTask demoTask; @Test
public void runDemo() throws Exception {
long start = System.currentTimeMillis();
demoTask.taskOne();
demoTask.taskTwo();
demoTask.taskThere();
long end = System.currentTimeMillis();
log.info("总任务执行结束,总耗时={} 毫秒", end - start);
}
}

输出日志

===执行任务1===
任务1执行结束,总耗时=204 毫秒
===执行任务2===
任务2执行结束,总耗时=203 毫秒
===执行任务3===
任务3执行结束,总耗时=201 毫秒
总任务执行结束,总耗时=613 毫秒

2、异步调用示例

异步方法

@Component
@Slf4j
public class AsyncTask { @Async
public void taskOne() throws Exception {
//执行内容同上,省略
} @Async
public void taskTwo() throws Exception {
//执行内容同上,省略
} @Async
public void taskThere() throws Exception {
//执行内容同上,省略
}
}

调用方法

@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@EnableAsync
@SpringBootTest
public class AsyncTest { @Autowired
private AsyncTask asyncTask; @Test
public void runAsync() throws Exception {
long start = System.currentTimeMillis();
asyncTask.taskOne();
asyncTask.taskTwo();
asyncTask.taskThere();
Thread.sleep(200);
long end = System.currentTimeMillis();
log.info("总任务执行结束,总耗时={} 毫秒", end - start);
}
}

查看日志

===执行任务1===
===执行任务3===
===执行任务2===
总任务执行结束,总耗时=206 毫秒
任务1执行结束,总耗时=200 毫秒
任务2执行结束,总耗时=201 毫秒
任务3执行结束,总耗时=201 毫秒

通过日志可以看出已经是已经实现异步处理任务了,而且异步任务哪个先执行是不确定的。

3、Future异步回调

如果我想异步执行,同时想获取所有异步执行的结果,那么这个时候就需要采用Future。

异步方法

@Component
@Slf4j
public class FutureTask { @Async
public Future<String> taskOne() throws Exception {
//执行内容同上,省略
return new AsyncResult<>("1完成");
}
@Async
public Future<String> taskTwo() throws Exception {
//执行内容同上,省略
return new AsyncResult<>("2完成");
} @Async
public Future<String> taskThere() throws Exception {
//执行内容同上,省略
return new AsyncResult<>("执行任务3完成");
}
}

调用方法

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
@EnableAsync
public class FutureTaskTest { @Autowired
private FutureTask futureTask; @Test
public void runAsync() throws Exception {
long start = System.currentTimeMillis();
Future<String> taskOne = futureTask.taskOne();
Future<String> taskTwo = futureTask.taskTwo();
Future<String> taskThere = futureTask.taskThere(); while (true) {
if (taskOne.isDone() && taskTwo.isDone() && taskThere.isDone()) {
log.info("任务1返回结果={},任务2返回结果={},任务3返回结果={},", taskOne.get(), taskTwo.get(), taskThere.get());
break;
}
}
long end = System.currentTimeMillis();
log.info("总任务执行结束,总耗时={} 毫秒", end - start);
}
}

查看日志

===执行任务2===
===执行任务3===
===执行任务1===
任务1执行结束,总耗时=201 毫秒
任务3执行结束,总耗时=201 毫秒
任务2执行结束,总耗时=201 毫秒
任务1返回结果=1完成,任务2返回结果=2完成,任务3返回结果=执行任务3完成,
总任务执行结束,总耗时=223 毫秒

从日志可以看出 异步任务的执行结果都有获取。

四、@Async+自定义线程池实现异步任务

如果不自定义异步方法的线程池默认使用SimpleAsyncTaskExecutor线程池。

SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。并发大的时候会产生严重的性能问题。

Spring也更加推荐我们开发者使用ThreadPoolTaskExecutor类来创建线程池。

自定义线程池

@Configuration
public class ExecutorAsyncConfig { @Bean(name = "newAsyncExecutor")
public Executor newAsync() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//设置核心线程数
taskExecutor.setCorePoolSize(10);
// 线程池维护线程的最大数量,只有在缓冲队列满了以后才会申请超过核心线程数的线程
taskExecutor.setMaxPoolSize(100);
//缓存队列
taskExecutor.setQueueCapacity(50);
//允许的空闲时间,当超过了核心线程数之外的线程在空闲时间到达之后会被销毁
taskExecutor.setKeepAliveSeconds(200);
//异步方法内部线程名称
taskExecutor.setThreadNamePrefix("my-xiaoxiao-AsyncExecutor-");
//拒绝策略
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.initialize();
return taskExecutor;
}
}

示例代码

任务1和任务2配置走我们自定义的线程池,任务3还是走默认线程池。

@Component
@Slf4j
public class FutureExecutorTask { @Async("newAsyncExecutor")
public Future<String> taskOne() throws Exception {
log.info("任务1线程名称 = {}", Thread.currentThread().getName());
return new AsyncResult<>("1完成");
}
@Async("newAsyncExecutor")
public Future<String> taskTwo() throws Exception {
log.info("任务2线程名称 = {}", Thread.currentThread().getName());
return new AsyncResult<>("2完成");
} @Async
public Future<String> taskThere() throws Exception {
log.info("任务3线程名称 = {}", Thread.currentThread().getName());
return new AsyncResult<>("执行任务3完成");
}
}

调研方法和上面一样,我们再来看下日志

任务2线程名称 = my-xiaoxiao-AsyncExecutor-2
任务1线程名称 = my-xiaoxiao-AsyncExecutor-1
任务3线程名称 = SimpleAsyncTaskExecutor-1
总任务执行结束,总耗时=15 毫秒

通过日志我们可以看出 任务1和任务2走的是我们自定义的线程池,任务3还是走默认线程池。

五、CompletableFuture实现异步任务

推荐这种方式来实现异步,它不需要在启动类上加@EnableAsync注解,也不需要在方法上加@Async注解,它实现更加优雅,而且CompletableFuture功能更加强大。

具体可以看下之前写的文章:异步编程利器:CompletableFuture

1、CompletableFuture示例

看如何使用

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class CompletableTest { @Autowired
private DemoTask dmoTask; @Test
public void testCompletableThenRunAsync() throws Exception {
long startTime = System.currentTimeMillis(); CompletableFuture<Void> cp1 = CompletableFuture.runAsync(() -> {
//任务1
dmoTask.taskOne();
});
CompletableFuture<Void> cp2 = CompletableFuture.runAsync(() -> {
//任务2
dmoTask.taskTwo();
});
CompletableFuture<Void> cp3 = CompletableFuture.runAsync(() -> {
//任务3
dmoTask.taskThere();
}); cp1.get();
cp2.get();
cp3.get();
//模拟主程序耗时时间
System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");
}
}

查看日志

===执行任务1===
===执行任务2===
===执行任务3===
任务3执行结束,总耗时=204 毫秒
任务2执行结束,总耗时=203 毫秒
任务1执行结束,总耗时=204 毫秒
总共用时226ms

从日志可以看出,通过CompletableFuture同样可以实现异步执行任务!

声明: 公众号如需转载该篇文章,发表文章的头部一定要 告知是转至公众号: 后端元宇宙。同时也可以问本人要markdown原稿和原图片。其它情况一律禁止转载!

SpringBoot使用@Async的总结!的更多相关文章

  1. SpringBoot系列——@Async优雅的异步调用

    前言 众所周知,java的代码是同步顺序执行,当我们需要执行异步操作时我们需要创建一个新线程去执行,以往我们是这样操作的: /** * 任务类 */ class Task implements Run ...

  2. SpringBoot中Async异步方法和定时任务介绍

    1.功能说明 Spring提供了Async注解来实现方法的异步调用. 即当调用Async标识的方法时,调用线程不会等待被调用方法执行完成即返回继续执行以下操作,而被调用的方法则会启动一个独立线程来执行 ...

  3. springboot 的 @Async

    /** * Created by zhiqi.shao on 2018/4/3. */ @EnableAsync @Configuration public class TaskPoolConfig ...

  4. SpringBoot使用@Async实现异步调用

    1.@EnableAsync 首先,我们需要在启动类上添加  @EnableAsync 注解来声明开启异步方法. @SpringBootApplication @EnableAsync public ...

  5. Spring Boot -- Spring Boot之@Async异步调用、Mybatis、事务管理等

    这一节将在上一节的基础上,继续深入学习Spring Boot相关知识,其中主要包括@Async异步调用,@Value自定义参数.Mybatis.事务管理等. 本节所使用的代码是在上一节项目代码中,继续 ...

  6. SpringBoot系列——事件发布与监听

    前言 日常开发中,我们经常会碰到这样的业务场景:用户注册,注册成功后需要发送邮箱.短信提示用户,通常我们都是这样写: /** * 用户注册 */ @GetMapping("/userRegi ...

  7. 免费IP代理池定时维护,封装通用爬虫工具类每次随机更新IP代理池跟UserAgent池,并制作简易流量爬虫

    前言 我们之前的爬虫都是模拟成浏览器后直接爬取,并没有动态设置IP代理以及UserAgent标识,本文记录免费IP代理池定时维护,封装通用爬虫工具类每次随机更新IP代理池跟UserAgent池,并制作 ...

  8. springboot--异步执行的方法及定时执行的方法

    让方法被调用后异步的执行 一般来说,要异步执行一个任务都是创建一个线程来专门干这个任务.在springboot中有 @Async 这个注解快速实现方法的异步执行.只需要两步:第一步: 在启动类上加上@ ...

  9. Spring及SpringBoot @Async配置步骤及注意事项

    前言 最近在做一个用户反馈的功能,就是当用户反馈意见或建议后服务端将意见保存然后发邮件给相关模块的开发者.考虑发邮件耗时的情况所以我想用异步的方法去执行,于是就开始研究Spring的@Async了.但 ...

  10. @Async异步注解与SpringBoot结合使用

    当你在service层需要启动异步线程去执行某些分支任务,又不希望显式使用Thread等线程相关类,只想专注于实现业务逻辑代码开发,可以使用@Async异步注解. 1. 使用@Async 异步注解 C ...

随机推荐

  1. 使用Inno Setup 制作软件安装包详细教程(与开发语言无关)

    前言:关于如何制作一个软件安装包的教程,与编程语言无关.以下,请看详情~ 1.下载Inno Setup,下载地址:https://jrsoftware.org/isinfo.php 2.下载最新版本即 ...

  2. Netty 学习(三):通信协议和编解码

    Netty 学习(三):通信协议和编解码 作者: Grey 原文地址: 博客园:Netty 学习(三):通信协议和编解码 CSDN:Netty 学习(三):通信协议和编解码 无论使用 Netty 还是 ...

  3. [Python]-openpyxl模块Excel数据处理-读取公式的结果

    日常需要Python来处理各种数据,处理Excel数据常用的库一般有openpyxl.xlrd(读取).xlwt(写入). 经过对比发现openpyxl模块比较好用. openpyxl模块 这篇笔记比 ...

  4. HK32F030MF4P6的Linux GCC工具链和VSCode开发环境

    HK32F030MF4P6简介 航顺的 HK32F030MF4P6, TSSOP20封装, Arm Cortex M0 内核, 内建32MHz时钟, 16K Flash, 2K RAM(实际上可用的有 ...

  5. Python使用tesserocr识别文字过程中遇到的一个问题

    最近在使用Python识别PNG图像中包含的文字时遇到一个问题.解决过程记录如下. (Python使用tesserocr的安装过程不再描述.) 在使用tesserocr识别PNG图像中的文字时,如果P ...

  6. 9个常用的Shell脚本

    1.Dos 攻击防范(自动屏蔽攻击 IP) #!/bin/bash DATE=$(date +%d/%b/%Y:%H:%M) LOG_FILE=/usr/local/nginx/logs/demo2. ...

  7. flask+gunicorn+nginx部署

    安装nginx和gunicorn yum install nginx pip3 install gunicorn flask项目配置 #main.py from flask import Flask ...

  8. JavaScript 的闭包(closure)

    以下内容为本人的学习笔记,如需要转载,请声明原文链接微信公众号「englyf」https://www.cnblogs.com/englyf/ 对于闭包的理解,其实可以归纳为,在创建函数时,同时创建了一 ...

  9. loam详细代码解析与公式推导

    loam详细代码解析与公式推导(基础理论知识) 一.基础坐标变换 loam中欧拉角解算都采用R P Y 的解算方式,即先左乘R, 再左乘P, 最后左乘Y,用矩阵表示为: R = Ry * Rp * R ...

  10. 【前端必会】走进webpack生命周期,另类的学习方法

    背景 webpack构建过程中的hooks都有什么呢?除了在网上看一些文章,还可以通过更直接的办法,结合官方文档快速让你进入webpack的hook世界 写一个入口文件 //index.js cons ...