性能提升!@Async与CompletableFuture优雅应用

开发 前端
如果异步方法不返回任何值,那么就很难知道方法执行时是否发生了异常。我们可以使用 AsyncUncaughtExceptionHandler 实现来捕获和处理此类异常。

1. 简介

@Async 和 CompletableFuture 是实现异步处理的强大工具组合。@Async 是Spring框架提供的一个注解,用于标记方法以表明它将在Spring管理的线程池中的另一个线程上异步执行。这使得开发人员能够在不阻塞主线程的情况下执行耗时的任务,从而提高应用程序的整体性能和响应速度。

CompletableFuture 是Java 8引入的一个强大的类,它代表了一个可能尚未完成的计算的结果。CompletableFuture 提供了丰富的API来支持异步编程模式,如回调、组合操作、错误处理等。通过将@Async与CompletableFuture结合使用,可以实现更高效的异步任务处理。

接下来,我们将介绍@Async与CompletableFuture结合的使用。

2. 实战案例

2.1 @EnableAsync and @Async

Spring 自带 @EnableAsync 注解,可应用于 @Configuration 类以实现异步行为。@EnableAsync 注解会查找标有 @Async 注解的方法,并在后台线程池中运行这些方法。

@Async 注解方法在单独的线程中执行,并返回 CompletableFuture 来保存异步计算的结果。

开启异步功能

@Configuration
@EnableAsync
public class AsyncConfig {
  @Bean(name = "asyncExecutor")
  public Executor asyncExecutor()  {
    int core = Runtime.getRuntime().availableProcessors() ;
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(core) ;
    executor.setMaxPoolSize(core) ;
    executor.setQueueCapacity(100) ;
    executor.setThreadNamePrefix("PackAsync-") ;
    executor.initialize() ;
    return executor ;
  }
}

如上我们自定义了线程池,该线程池用来执行我们的异步任务。你也可以不用配置,使用系统默认的线程池。

创建异步任务

@Async("asyncExecutor")
public CompletableFuture<EmployeeNames> task() {
  // TODO
}

用 @Async 对方法进行注解,该方法应异步运行。该方法必须是公共的,可以返回值,也可以不返回值。如果返回值,则应使用 Future 接口实现对其进行封装。

这里指定了使用我们自定义的线程池执行异步任务。

多个异步任务同时执行

CompletableFuture.allOf(
  asyncMethodOne, 
  asyncMethodTwo, 
  asyncMethodThree
).join() ;

要合并多个异步任务的结果,通过使用 join() 方法,这将等待所有异步任务执行完成才会继续往后执行。

2.2 Rest Controller中调用异步任务

接下来,我们将创建一个 REST API,从三个远程服务异步获取数据,当所有三个服务的响应都可用时,再汇总响应。

  • 调用/addresses接口获取所有地址信息
  • 调用/phones接口获取所有电话数据
  • 调用/names接口获取所有姓名
  • 等待以上3个接口都返回结果后再进行处理
  • 汇总所有三个应用程序接口的响应,并生成最终响应发送回客户端 

远程接口准备

@RestController
public class EmployeeController {
  @GetMapping("/addresses")
  public EmployeeAddresses addresses() {
    // TODO
  }
  @GetMapping("/phones")
  public EmployeePhone phones() {
    // TODO
  }
  @GetMapping("/names")
  public EmployeeNames names() {
    // TODO
  }
}

我们将通过异步的方式调用上面定义的3个接口。

异步调用REST API

这些服务方法将从远程应用程序接口或数据库中提取数据,必须在不同的线程中并行运行,以加快处理速度。

@Service
public class AsyncService {
  private static Logger logger = LoggerFactory.getLogger(AsyncService.class);
  private final RestTemplate restTemplate;
  public AsyncService(RestTemplate restTemplate) {
    this.restTemplate = restTemplate ;
  }
  @Async("asyncExecutor")
  public CompletableFuture<EmployeeNames> names()  {
    logger.info("getEmployeeName starts");
    EmployeeNames employeeNameData = restTemplate.getForObject("http://localhost:8080/names", EmployeeNames.class) ;
    logger.info("employeeNameData, {}", employeeNameData) ;
    logger.info("employeeNameData completed");
    return CompletableFuture.completedFuture(employeeNameData);
  }
  @Async("asyncExecutor")
  public CompletableFuture<EmployeeAddresses> addresses() {
    logger.info("getEmployeeAddress starts");
    EmployeeAddresses employeeAddressData = restTemplate.getForObject("http://localhost:8080/addresses", EmployeeAddresses.class);
    logger.info("employeeAddressData, {}", employeeAddressData) ;
    logger.info("employeeAddressData completed");
    return CompletableFuture.completedFuture(employeeAddressData);
  }
  @Async("asyncExecutor")
  public CompletableFuture<EmployeePhone> phones() {
    logger.info("getEmployeePhone starts") ;
    EmployeePhone employeePhoneData = restTemplate.getForObject("http://localhost:8080/phones", EmployeePhone.class) ;
    logger.info("employeePhoneData, {}", employeePhoneData) ;
    logger.info("employeePhoneData completed") ;
    return CompletableFuture.completedFuture(employeePhoneData) ;
  }
}

注意:你可不能如下方式来执行远程接口的调用。

CompletableFuture.supplyAsync(() -> {
  return restTemplate.getForObject("http://localhost:8080/phones", EmployeePhone.class) ;
}) ;

如果你这样写,你的远程接口并非在你的异步线程中执行,而是在CompletableFuturue的线程池中执行(ForkJoinPool)。

2.3 聚合异步任务

接下来在REST API中调用上面的异步方法、消耗和聚合其响应并返回客户端。

@RestController
public class AsyncController {
  private final AsyncService asyncService;
  public AsyncController(AsyncService service) {
    this.asyncService = asyncService ;
  }


  @GettMapping("/profile/infos")
  public EmployeeDTO infos() throws Exception {
    CompletableFuture<EmployeeAddresses> addresses = asyncService.addresses() ;
    CompletableFuture<EmployeeNames> names = asyncService.names() ;
    CompletableFuture<EmployeePhone> phones = asyncService.phones() ;
    // 等待所有异步任务都执行完成
    CompletableFuture.allOf(addresses, names, phones).join() ;
    return new EmployeeDTO(addresses.get(), names.get(), phones.get()) ;
  }
}

整个请求的耗时将会是请求最长REST API调用所用的时间,这大大提升该接口的性能。

2.4 异常处理

当方法的返回类型是 Future 时,Future.get() 方法会抛出异常,我们应该在聚合结果之前使用 try-catch 块捕获并处理异常。

问题是,如果异步方法不返回任何值,那么就很难知道方法执行时是否发生了异常。我们可以使用 AsyncUncaughtExceptionHandler 实现来捕获和处理此类异常。

@Configuration
public class AsyncConfig implements AsyncConfigurer {


  @Override
  public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    return new AsyncExceptionHandler() ;
  }


  public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
    private final Logger logger = LoggerFactory.getLogger(AsyncExceptionHandler.class);
    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
      logger.error("Unexpected asynchronous exception at : "
        + method.getDeclaringClass().getName() + "." + method.getName(), ex);
    }
  }
}

责任编辑:武晓燕 来源: Spring全家桶实战案例源码
相关推荐

2024-08-06 09:43:54

Java 8工具编程

2024-09-14 11:31:27

@AsyncSpring异步

2024-08-30 09:53:17

Java 8编程集成

2018-09-18 16:20:08

Asyncjavascript前端

2023-11-06 08:01:09

Go同步异步

2023-04-09 16:34:49

JavaSemaphore开发

2024-09-19 08:09:37

MySQL索引数据库

2011-10-17 09:47:53

应用性能工作负载服务器

2009-08-25 15:35:45

citrxinetscalerncore

2020-10-27 08:24:01

Java

2023-08-31 19:17:23

2015-01-21 15:40:44

GoRuby

2018-12-10 15:13:06

缓存系统性能数据

2022-11-27 08:12:11

RocketMQ源码工具类

2012-09-04 09:18:02

NPBBYOD

2023-11-06 09:32:52

Java实践

2010-01-21 23:29:06

戴尔银行东亚银行

2009-06-26 14:13:38

OSCache

2013-12-02 17:33:52

Radware

2011-09-20 10:41:45

Web
点赞
收藏

51CTO技术栈公众号