Spring Boot使用@Async实现异步调用

异步调用对应的是同步调用,同步调用可以理解为按照定义的顺序依次执行,有序性;异步调用在执行的时候不需要等待上一个指令调用结束就可以继续执行。

我们将在创建一个 Spring Boot 工程来说明。具体工程可以参考github代码 https://github.com/UniqueDong/springboot-study async模块

pom 依赖如下:

1<dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter</artifactId> 4 <exclusions> 5 <exclusion> 6 <artifactId>spring-boot-starter-logging</artifactId> 7 <groupId>org.springframework.boot</groupId> 8 </exclusion> 9 </exclusions> 10 </dependency> 11 <!-- logback --> 12 <dependency> 13 <groupId>ch.qos.logback</groupId> 14 <artifactId>logback-access</artifactId> 15 </dependency> 16 <dependency> 17 <groupId>ch.qos.logback</groupId> 18 <artifactId>logback-classic</artifactId> 19 </dependency> 20 <dependency> 21 <groupId>ch.qos.logback</groupId> 22 <artifactId>logback-core</artifactId> 23 </dependency> 24 25 <dependency> 26 <groupId>org.springframework.boot</groupId> 27 <artifactId>spring-boot-starter-aop</artifactId> 28 </dependency> 29 30 <dependency> 31 <groupId>org.projectlombok</groupId> 32 <artifactId>lombok</artifactId> 33 <version>1.18.2</version> 34 <optional>true</optional> 35 </dependency> 36 <dependency> 37 <groupId>org.springframework.boot</groupId> 38 <artifactId>spring-boot-starter-test</artifactId> 39 <scope>test</scope> 40 </dependency>

启动类如下:

1@SpringBootApplication 2public class AsyncApplication { 3 4 public static void main(String[] args) { 5 SpringApplication.run(AsyncApplication.class, args); 6 } 7 8}

定义线程池

1import org.springframework.context.annotation.Bean; 2import org.springframework.context.annotation.Configuration; 3import org.springframework.scheduling.annotation.EnableAsync; 4import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 5 6import java.util.concurrent.Executor; 7import java.util.concurrent.ThreadPoolExecutor; 8 9/** 10 * 异步线程池 11 */ 12@Configuration 13@EnableAsync 14public class AsyncExecutorConfig { 15 16 /** 17 * Set the ThreadPoolExecutor's core pool size. 18 */ 19 private int corePoolSize = 8; 20 /** 21 * Set the ThreadPoolExecutor's maximum pool size. 22 */ 23 private int maxPoolSize = 16; 24 /** 25 * Set the capacity for the ThreadPoolExecutor's BlockingQueue. 26 */ 27 private int queueCapacity = 200; 28 29 private String threadNamePrefix = "AsyncExecutor-"; 30 31 @Bean("taskExecutor") 32 public Executor taskExecutor() { 33 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 34 executor.setCorePoolSize(corePoolSize); 35 executor.setMaxPoolSize(maxPoolSize); 36 executor.setQueueCapacity(queueCapacity); 37 executor.setKeepAliveSeconds(60); 38 executor.setThreadNamePrefix(threadNamePrefix); 39 40 // rejection-policy:当pool已经达到max size的时候,如何处理新任务 41 // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行 42 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); 43 executor.initialize(); 44 return executor; 45 } 46 47} 48

代码中我们通过 ThreadPoolTaskExecutor 创建了一个线程池。参数含义如下所示:

  • corePoolSize:线程池创建的核心线程数
  • maxPoolSize:线程池最大线程池数量,当任务数超过corePoolSize以及缓冲队列也满了以后才会申请的线程数量。
  • setKeepAliveSeconds: 允许线程空闲时间60秒,当maxPoolSize的线程在空闲时间到达的时候销毁。
  • ThreadNamePrefix:线程的前缀任务名字。
  • RejectedExecutionHandler:当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务

使用实战

1@Slf4j 2@Service 3public class OrderService { 4 public static Random random = new Random(); 5 6 7 @Autowired 8 private AsyncTask asyncTask; 9 10 public void doShop() { 11 try { 12 createOrder(); 13 // 调用有结果返回的异步任务 14 Future<String> pay = asyncTask.pay(); 15 if (pay.isDone()) { 16 try { 17 String result = pay.get(); 18 log.info("异步任务返回结果{}", result); 19 } catch (ExecutionException e) { 20 e.printStackTrace(); 21 } 22 asyncTask.vip(); 23 asyncTask.sendSms(); 24 } 25 otherJob(); 26 } catch (InterruptedException e) { 27 log.error("异常", e); 28 } 29 } 30 31 public void createOrder() { 32 log.info("开始做任务1:下单成功"); 33 } 34 35 /** 36 * 错误使用,不会异步执行:调用方与被调方不能在同一个类。主要是使用了动态代理,同一个类的时候直接调用,不是通过生成的动态代理类调用 37 */ 38 @Async("taskExecutor") 39 public void otherJob() { 40 log.info("开始做任务4:物流"); 41 long start = System.currentTimeMillis(); 42 try { 43 Thread.sleep(random.nextInt(10000)); 44 } catch (InterruptedException e) { 45 e.printStackTrace(); 46 } 47 long end = System.currentTimeMillis(); 48 log.info("完成任务4,耗时:" + (end - start) + "毫秒"); 49 } 50 51}

异步任务服务类

1mport lombok.extern.slf4j.Slf4j; 2import org.springframework.scheduling.annotation.Async; 3import org.springframework.scheduling.annotation.AsyncResult; 4import org.springframework.stereotype.Component; 5 6import java.util.Random; 7import java.util.concurrent.Future; 8 9@Component 10@Slf4j 11public class AsyncTask { 12 public static Random random = new Random(); 13 14 15 @Async("taskExecutor") 16 public void sendSms() throws InterruptedException { 17 log.info("开始做任务2:发送短信"); 18 long start = System.currentTimeMillis(); 19 Thread.sleep(random.nextInt(10000)); 20 long end = System.currentTimeMillis(); 21 log.info("完成任务1,耗时:" + (end - start) + "毫秒"); 22 } 23 24 @Async("taskExecutor") 25 public Future<String> pay() throws InterruptedException { 26 log.info("开始做异步返回结果任务2:支付"); 27 long start = System.currentTimeMillis(); 28 Thread.sleep(random.nextInt(10000)); 29 long end = System.currentTimeMillis(); 30 log.info("完成任务2,耗时:" + (end - start) + "毫秒"); 31 return new AsyncResult<>("会员服务完成"); 32 } 33 34 /** 35 * 返回结果的异步调用 36 * @throws InterruptedException 37 */ 38 @Async("taskExecutor") 39 public void vip() throws InterruptedException { 40 log.info("开始做任务5:会员"); 41 long start = System.currentTimeMillis(); 42 Thread.sleep(random.nextInt(10000)); 43 long end = System.currentTimeMillis(); 44 log.info("开始做异步返回结果任务5,耗时:" + (end - start) + "毫秒"); 45 } 46}

单元测试

1@RunWith(SpringRunner.class) 2@SpringBootTest(classes = AsyncApplication.class) 3public class AsyncApplicationTests { 4 5 @Autowired 6 private OrderService orderService; 7 8 @Test 9 public void testAsync() { 10 orderService.doShop(); 11 try { 12 Thread.currentThread().join(); 13 } catch (InterruptedException e) { 14 e.printStackTrace(); 15 } 16 } 17 18}

结果展示

12019-05-16 20:25:06.577 [INFO ] [main] - zero.springboot.study.async.service.OrderService-52 开始做任务1:下单成功 22019-05-16 20:25:06.586 [INFO ] [main] - zero.springboot.study.async.service.OrderService-60 开始做任务4:物流 32019-05-16 20:25:06.599 [INFO ] [AsyncExecutor-1] - zero.springboot.study.async.service.AsyncTask-38 开始做异步返回结果任务2:支付 42019-05-16 20:25:13.382 [INFO ] [AsyncExecutor-1] - zero.springboot.study.async.service.AsyncTask-42 完成任务2,耗时:6783毫秒 52019-05-16 20:25:14.771 [INFO ] [main] - zero.springboot.study.async.service.OrderService-68 完成任务4,耗时:8184毫秒

可以看到有的线程的名字就是我们线程池定义的前缀,说明使用了线程池异步执行。其中我们示范了一个错误的使用案例 otherJob(),并没有异步执行。

原因:

spring 在扫描bean的时候会扫描方法上是否包含@Async注解,如果包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),代理类是继承原来那个bean的。此时,当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用时增加异步作用。然而,如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个 bean 也就是 this. method,所以就没有增加异步作用,我们看到的现象就是@Async注解无效。

关注公众号获取最新文章一起进步JavaStorm.png

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )