@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);
    }
  }
}
相关推荐
fox_mt16 分钟前
AI Coding - ClaudeCode使用指南
java·ai编程
min18112345619 分钟前
PC端零基础跨职能流程图制作教程
大数据·人工智能·信息可视化·架构·流程图
毕设源码-郭学长33 分钟前
【开题答辩全过程】以 基于SSM的高校运动会管理系统的设计与实现为例,包含答辩的问题和答案
java·eclipse
qq_54702617936 分钟前
Maven 使用指南
java·maven
静听松涛13337 分钟前
中文PC端多人协作泳道图制作平台
大数据·论文阅读·人工智能·搜索引擎·架构·流程图·软件工程
xiaolyuh12344 分钟前
Arthas修改类(如加日志)的实现原理
java
栗子叶1 小时前
Java对象创建的过程
java·开发语言·jvm
勇哥java实战分享1 小时前
短信平台 Pro 版本 ,比开源版本更强大
后端
学历真的很重要1 小时前
LangChain V1.0 Context Engineering(上下文工程)详细指南
人工智能·后端·学习·语言模型·面试·职场和发展·langchain
有一个好名字1 小时前
力扣-从字符串中移除星号
java·算法·leetcode