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

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 来保存异步计算的结果。

开启异步功能

ini 复制代码
@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 ;
  }
}

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

创建异步任务

typescript 复制代码
@Async("asyncExecutor")
public CompletableFuture<EmployeeNames> task() {
  // TODO
}

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

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

多个异步任务同时执行

scss 复制代码
CompletableFuture.allOf(
  asyncMethodOne, 
  asyncMethodTwo, 
  asyncMethodThree
).join() ;

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

2.2 Rest Controller中调用异步任务

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

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

远程接口准备

less 复制代码
@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

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

kotlin 复制代码
@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) ;
  }
}

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

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

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

2.3 聚合异步任务

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

kotlin 复制代码
@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 实现来捕获和处理此类异常。

typescript 复制代码
@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);
    }
  }
}
相关推荐
zfoo-framework4 分钟前
【jenkins插件】
java
风_流沙9 分钟前
java 对ElasticSearch数据库操作封装工具类(对你是否适用嘞)
java·数据库·elasticsearch
Hacker_Fuchen26 分钟前
天融信网络架构安全实践
网络·安全·架构
颜淡慕潇32 分钟前
【K8S问题系列 |19 】如何解决 Pod 无法挂载 PVC问题
后端·云原生·容器·kubernetes
ProtonBase39 分钟前
如何从 0 到 1 ,打造全新一代分布式数据架构
java·网络·数据库·数据仓库·分布式·云原生·架构
乐之者v1 小时前
leetCode43.字符串相乘
java·数据结构·算法
suweijie7684 小时前
SpringCloudAlibaba | Sentinel从基础到进阶
java·大数据·sentinel
公贵买其鹿5 小时前
List深拷贝后,数据还是被串改
java
向前看-8 小时前
验证码机制
前端·后端
xlsw_8 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis