通常我们开发的程序都是同步调用的,即程序按照代码的顺序一行一行的逐步往下执行,每一行代码都必须等待上一行代码执行完毕才能开始执行。而异步编程则没有这个限制,代码的调用不再是阻塞的。所以在一些情景下,通过异步编程可以提高效率,提升接口的吞吐量。这节将介绍如何在Spring Boot中进行异步编程。

开启异步

新建一个Spring Boot项目,版本为2.1.0.RELEASE,并引入spring-boot-starter-web依赖,项目结构如下所示:

要开启异步支持,首先得在Spring Boot入口类上加上@EnableAsync注解:

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

接下来开始编写异步方法。

在com.example.demo路径下新建service包,并创建TestService:

@Service
public class TestService {

  private Logger logger = LoggerFactory.getLogger(this.getClass());

  @Async
  public void asyncMethod() {
      sleep();
      logger.info("异步方法内部线程名称:{}", Thread.currentThread().getName());
  }

  public void syncMethod() {
      sleep();
  }

  private void sleep() {
      try {
          TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
  }
}

上面的Service中包含一个异步方法asyncMethod(开启异步支持后,只需要在方法上加上@Async注解便是异步方法了)和同步方法syncMethod。sleep方法用于让当前线程阻塞2秒钟。

接着在com.example.demo路径下新建controller包,然后创建TestController:

@RestController
public class TestController {

  private Logger logger = LoggerFactory.getLogger(this.getClass());

  @Autowired
  private TestService testService;

  @GetMapping("async")
  public void testAsync() {
      long start = System.currentTimeMillis();
      logger.info("异步方法开始");

      testService.asyncMethod();

      logger.info("异步方法结束");
      long end = System.currentTimeMillis();
      logger.info("总耗时:{} ms", end - start);
  }

  @GetMapping("sync")
  public void testSync() {
      long start = System.currentTimeMillis();
      logger.info("同步方法开始");

      testService.syncMethod();

      logger.info("同步方法结束");
      long end = System.currentTimeMillis();
      logger.info("总耗时:{} ms", end - start);
  }
}

启动项目,访问 http://localhost:8080/sync 请求,控制台输出如下:

可看到默认程序是同步的,由于sleep方法阻塞的原因,testSync方法执行了2秒钟以上。

访问 http://localhost:8080/async ,控制台输出如下:

可看到testAsync方法耗时极少,因为异步的原因,程序并没有被sleep方法阻塞,这就是异步调用的好处。同时异步方法内部会新启一个线程来执行,这里线程名称为task - 1。

默认情况下的异步线程池配置使得线程不能被重用,每次调用异步方法都会新建一个线程,我们可以自己定义异步线程池来优化。

自定义异步线程池

在com.example.demo下新建config包,然后创建AsyncPoolConfig配置类:

@Configuration
public class AsyncPoolConfig {

  @Bean
  public ThreadPoolTaskExecutor asyncThreadPoolTaskExecutor(){
      ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
      executor.setCorePoolSize(20);
      executor.setMaxPoolSize(200);
      executor.setQueueCapacity(25);
      executor.setKeepAliveSeconds(200);
      executor.setThreadNamePrefix("asyncThread");
      executor.setWaitForTasksToCompleteOnShutdown(true);
      executor.setAwaitTerminationSeconds(60);

      executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

      executor.initialize();
      return executor;
  }
}

上面我们通过ThreadPoolTaskExecutor的一些方法自定义了一个线程池,这些方法的含义如下所示:

  • corePoolSize:线程池核心线程的数量,默认值为1(这就是默认情况下的异步线程池配置使得线程不能被重用的原因)。

  • maxPoolSize:线程池维护的线程的最大数量,只有当核心线程都被用完并且缓冲队列满后,才会开始申超过请核心线程数的线程,默认值为Integer.MAX_VALUE。

  • queueCapacity:缓冲队列。

  • keepAliveSeconds:超出核心线程数外的线程在空闲时候的最大存活时间,默认为60秒。

  • threadNamePrefix:线程名前缀。

  • waitForTasksToCompleteOnShutdown:是否等待所有线程执行完毕才关闭线程池,默认值为false。

  • awaitTerminationSeconds:waitForTasksToCompleteOnShutdown的等待的时长,默认值为0,即不等待。

  • rejectedExecutionHandler:当没有线程可以被使用时的处理策略(拒绝任务),默认策略为abortPolicy,包含下面四种策略:

    1. callerRunsPolicy:用于被拒绝任务的处理程序,它直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务。

    2. abortPolicy:直接抛出java.util.concurrent.RejectedExecutionException异常。

    3. discardOldestPolicy:当线程池中的数量等于最大线程数时、抛弃线程池中最后一个要执行的任务,并执行新传入的任务。

    4. discardPolicy:当线程池中的数量等于最大线程数时,不做任何动作。

要使用该线程池,只需要在@Async注解上指定线程池Bean名称即可:

@Service
public class TestService {
  ......

  @Async("asyncThreadPoolTaskExecutor")
  public void asyncMethod() {
      ......
  }
  ......
}

重启项目,再次访问 http://localhost:8080/async ,控制台输出入下:

处理异步回调

如果异步方法具有返回值的话,需要使用Future来接收回调值。我们修改TestService的asyncMethod方法,给其添加返回值:

@Async("asyncThreadPoolTaskExecutor")
public Future<String> asyncMethod() {
  sleep();
  logger.info("异步方法内部线程名称:{}", Thread.currentThread().getName());
  return new AsyncResult<>("hello async");
}

泛型指定返回值的类型,AsyncResult为Spring实现的Future实现类:

接着改造TestController的testAsync方法:

@GetMapping("async")
public String testAsync() throws Exception {
  long start = System.currentTimeMillis();
  logger.info("异步方法开始");

  Future<String> stringFuture = testService.asyncMethod();
  String result = stringFuture.get();
  logger.info("异步方法返回值:{}", result);
   
  logger.info("异步方法结束");

  long end = System.currentTimeMillis();
  logger.info("总耗时:{} ms", end - start);
  return stringFuture.get();
}

Future接口的get方法用于获取异步调用的返回值。

重启项目,访问 http://localhost:8080/async 控制台输出如下所示:

通过返回结果我们可以看出Future的get方法为阻塞方法,只有当异步方法返回内容了,程序才会继续往下执行。get还有一个get(long timeout, TimeUnit unit)重载方法,我们可以通过这个重载方法设置超时时间,即异步方法在设定时间内没有返回值的话,直接抛出java.util.concurrent.TimeoutException异常。

比如设置超时时间为60秒:

String result = stringFuture.get(60, TimeUnit.SECONDS);

Spring Boot 中的异步调用的更多相关文章

  1. Spring Boot中实现异步调用之@Async

    一.什么是异步调用 “异步调用”对应的是“同步调用”,同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行:异步调用指程序在顺序执行时,不等待异步调用 的语句返回结果 ...

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

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

  3. (转)spring boot注解 --@EnableAsync 异步调用

    原文:http://www.cnblogs.com/azhqiang/p/5609615.html EnableAsync注解的意思是可以异步执行,就是开启多线程的意思.可以标注在方法.类上. @Co ...

  4. spring boot注解 --@EnableAsync 异步调用

    EnableAsync注解的意思是可以异步执行,就是开启多线程的意思.可以标注在方法.类上. @Component public class Task { @Async public void doT ...

  5. 【Spring Boot学习之六】Spring Boot整合定时任务&异步调用

    环境 eclipse 4.7 jdk 1.8 Spring Boot 1.5.2一.定时任务1.启动类添加注解@EnableScheduling 用于开启定时任务 package com.wjy; i ...

  6. Spring Boot 中如何支持异步方法

    本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...

  7. spring boot中使用@Async实现异步调用任务

    本篇文章主要介绍了spring boot中使用@Async实现异步调用任务,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 什么是“异步调用”? “异步调用”对应的是“同步 ...

  8. Spring Boot中使用@Async实现异步调用

    在Spring Boot中,我们只需要通过使用@Async注解就能简单的将原来的同步函数变为异步函数,为了让@Async注解能够生效,还需要在Spring Boot的主程序中配置@EnableAsyn ...

  9. 56. spring boot中使用@Async实现异步调用【从零开始学Spring Boot】

    什么是"异步调用"? "异步调用"对应的是"同步调用",同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执 ...

  10. Spring Boot中使用@Async实现异步调用,加速任务的执行!

    什么是"异步调用"?"异步调用"对应的是"同步调用",同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行 ...

随机推荐

  1. asp.net core 浏览器向服务端传递对象或对象数组参数服务端接收方式

    日常开发中我们经常会在客户端向服务器端传递参数,下面以asp.net core为例 专门分享传递对象或对象数组方法 一.键值对专递对象 以下是一个表单,现在需求是将以下 表单 所有input元素以独享 ...

  2. spring boot整合druid

    其实网上有很多例子可供参考,主要是在整合的过程中遇到了一些问题,方便记录下.另外例子可参考以下两个链接: https://www.jianshu.com/p/e3cd2e1c2b0c https:// ...

  3. HDLbits——Rotate100

    verilog代码: // Build a 100-bit left/right rotator, with synchronous load and left/right enable. //A r ...

  4. 封装ESXi网卡驱动

    只能使用 Windows 7 系统 只能使用 Windows 7 系统 只能使用 Windows 7 系统 OK,留下的继续吧,

  5. Github快速访问

    Github快速访问 1. 国内访问github慢     github是国外网站,用国内的网络很难访问到,也就无法使用github,作为程序猿的我们,无法使用github可太难受了,那么我们有什么办 ...

  6. centos-7实现批量ping

    批量ping 脚本 新接手了一个项目,10多个网段,500多机器,看自己机器可以连上多少服务器,一个个ping不符合我们的作风,因此百度了下大神的脚本,按自己的风格修改了,于是有了如下的脚本. 脚本如 ...

  7. zookeeper设置开机自启

    开机自启:(1)编辑zookeeper.service文件 vim /usr/lib/systemd/system/zookeeper.service 加入如下内容复制代码[Unit]Descript ...

  8. 使用CefSharp获取A标签中的内容

    js = @"(function (){ return document.querySelectorAll('a')[0].innerHTML})(); "; chromeBrow ...

  9. 个人js基础知识及看js高级程序设计查漏 汇总

    1.事件循环机制 js单线程操作. 1>主线程读取js代码 ,此时为同步环境,形成相应的堆和执行栈. 2>主线程遇到异步任务,指给对应的异步进程处理. 3>异步进程处理完毕后,将相应 ...

  10. node.js请求css、js静态资源页面不生效

    产生原因:文件响应头内容类型错误 解决方案:设置对应的响应头内容类型 const http = require('http'); const fs = require('fs'); const pat ...