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

之前有写过一篇文章叫做: 异步编程利器: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. python 模块、原始字符串

    模块 三种方法: import from 模块 import 成员,成员 from 模块 import * *代表所有的成员 隐藏成员: 模块中以下划线_开头的属性 隐藏成员不会被from 模块 im ...

  2. Java SE 19 新增特性

    Java SE 19 新增特性 作者:Grey 原文地址: 博客园:Java SE 19 新增特性 CSDN:Java SE 19 新增特性 源码 源仓库: Github:java_new_featu ...

  3. 安装Alertmanager,nginx配置二级路径代理访问

    安装配置 Alertmanager wget https://github.com/prometheus/alertmanager/releases/download/v0.20.0/alertman ...

  4. SonarQube 插件之 Issues Report & SonarLint 的配置及使用

    转载自:https://cloud.tencent.com/developer/article/1010599 1.Issues Report Plugins 介绍 使用 Issues Report ...

  5. Docker/K8s 解决容器内时区不一致方案

    转载自:https://cloud.tencent.com/developer/article/1433215 1.背景介绍 我们知道,使用 docker 容器启动服务后,如果使用默认 Centos ...

  6. 内网横向渗透 之 ATT&CK系列一 之 拿下域控制器

    信息收集 信息收集 域控制器的相关信息: 通过arp扫描发现域控制器的ip地址为:192.168.52.138,尝试使用msf的smb_login模块登录smb是否成功 1 search smb_lo ...

  7. 1-VSCode搭建GD32开发环境

    一.使用VSCode开发GD32的原因 1-单片机开发用的最多的IDE为Keil,而Keil为商用软件,并非开源,而且只支持windows环境,介于当前关系,有断供的风险在. 2-其他IDE类似第1条 ...

  8. Java删除word合并单元格时的重复值

    Spire.Doc提供了Table.applyVerticalMerge()方法来垂直合并word文档里面的表格单元格,Table.applyHorizontalMerge()方法来水平合并表格单元格 ...

  9. Redis实现布隆过滤器解析

    布隆过滤器原理介绍 [1]概念说明 1)布隆过滤器(Bloom Filter)是1970年由布隆提出的.它实际上是一个很长的二进制向量和一系列随机映射函数.布隆过滤器可以用于检索一个元素是否在一个集合 ...

  10. .NET 5 设计 API (资源站)

    跟新于 2022-11日 数据抓取端 随着数据的增多,问题也越来越多 用redis 主要是为了 以后进行,多个数据库写入. 例如我搭建一个 别的数据库论坛,我直接拿数据去redis里面拿,就不用跨库查 ...