当前位置:  首页>> 技术小册>> Java并发编程实战

24 | CompletableFuture:异步编程没那么难

在Java的并发编程领域,随着应用程序对性能要求的不断提高,异步编程逐渐成为处理复杂任务、提升程序响应性和吞吐量的重要手段。然而,传统的异步编程模式,如使用Future接口,虽然能够实现异步执行,但在处理复杂逻辑、组合多个异步任务以及异常处理方面显得力不从心。Java 8引入的CompletableFuture类,正是为了解决这些问题而设计的,它极大地简化了异步编程的复杂性,让开发者能够以更直观、更灵活的方式编写异步代码。

24.1 初探CompletableFuture

CompletableFutureFuture接口的一个增强版,它实现了FutureCompletionStage两个接口。与Future相比,CompletableFuture提供了更为丰富的API,支持异步计算完成时的回调、组合多个CompletableFuture实例以及更精细的异常处理机制。通过这些特性,CompletableFuture能够让你以声明性的方式编写复杂的异步逻辑,而无需陷入繁琐的回调地狱。

24.2 创建CompletableFuture

CompletableFuture提供了多种静态方法来创建其实例,以适应不同的场景:

  • supplyAsync(Supplier<? extends T> supplier, Executor executor):异步执行给定的Supplier函数式接口,返回一个包含其结果的CompletableFuture。可以指定执行器Executor来控制异步任务的执行线程。
  • runAsync(Runnable runnable, Executor executor):异步执行给定的Runnable任务,不返回结果。同样可以指定执行器。
  • completedFuture(T value):返回一个已完成的CompletableFuture,其结果已经预先设定。

24.3 异步结果的处理

一旦你有了CompletableFuture实例,就可以通过一系列的方法来处理异步计算的结果或异常:

  • thenApply(Function<? super T,? extends U> fn):当CompletableFuture正常完成时,应用给定的函数到其结果上,并返回一个新的CompletableFuture,该CompletableFuture的结果是函数调用的结果。
  • thenAccept(Consumer<? super T> consumer):当CompletableFuture正常完成时,执行给定的Consumer动作。
  • thenRun(Runnable runnable):当CompletableFuture正常完成时,执行给定的Runnable任务。
  • exceptionally(Function fn):当CompletableFuture异常完成时,应用给定的函数到异常上,并返回一个新的CompletableFuture,该CompletableFuture以函数的返回值作为结果。

24.4 组合CompletableFuture

CompletableFuture的真正强大之处在于它能够以声明性的方式组合多个异步任务,从而构建复杂的异步流程。以下是一些组合方法:

  • thenCombine(CompletableFuture<? extends U> other, BiFunction<? super T,? super U,? extends V> fn):当两个CompletableFuture都完成时,将它们的结果作为参数传递给给定的BiFunction,并返回一个新的CompletableFuture,其结果是BiFunction的返回值。
  • thenAcceptBoth(CompletableFuture<? extends U> other, BiConsumer<? super T,? super U> consumer):当两个CompletableFuture都完成时,执行给定的BiConsumer动作。
  • applyToEither(CompletableFuture<? extends T> other, Function<? super T,U> fn):两个CompletableFuture中任意一个完成时,将完成的结果作为参数传递给给定的Function,并返回一个新的CompletableFuture,其结果是Function的返回值。如果两个CompletableFuture都异常完成,则返回的CompletableFuture也异常完成,异常是第一个遇到的异常。
  • acceptEither(CompletableFuture<? extends T> other, Consumer<? super T> consumer):两个CompletableFuture中任意一个完成时,执行给定的Consumer动作。

24.5 实战案例

假设我们有一个电商网站,需要处理用户的订单。订单处理涉及多个步骤,如库存检查、支付验证和订单确认,每个步骤都可能是异步的。我们可以使用CompletableFuture来优雅地实现这一过程。

  1. CompletableFuture<Void> checkInventory = CompletableFuture.runAsync(() -> {
  2. // 异步检查库存
  3. System.out.println("Checking inventory...");
  4. // 模拟耗时操作
  5. try {
  6. Thread.sleep(1000);
  7. } catch (InterruptedException e) {
  8. Thread.currentThread().interrupt();
  9. }
  10. System.out.println("Inventory checked.");
  11. });
  12. CompletableFuture<Void> verifyPayment = CompletableFuture.runAsync(() -> {
  13. // 异步验证支付
  14. System.out.println("Verifying payment...");
  15. // 模拟耗时操作
  16. try {
  17. Thread.sleep(1500);
  18. } catch (InterruptedException e) {
  19. Thread.currentThread().interrupt();
  20. }
  21. System.out.println("Payment verified.");
  22. });
  23. CompletableFuture<Void> confirmOrder = checkInventory.thenRun(() -> {
  24. // 库存检查通过后,确认订单
  25. System.out.println("Confirming order...");
  26. // 模拟耗时操作
  27. try {
  28. Thread.sleep(500);
  29. } catch (InterruptedException e) {
  30. Thread.currentThread().interrupt();
  31. }
  32. System.out.println("Order confirmed.");
  33. });
  34. CompletableFuture<Void> allDone = confirmOrder.thenCombine(verifyPayment, (unused1, unused2) -> null);
  35. allDone.join(); // 等待所有任务完成
  36. System.out.println("Order processing completed.");

在这个例子中,我们创建了两个异步任务checkInventoryverifyPayment,分别用于检查库存和验证支付。然后,我们使用thenRun在库存检查完成后确认订单。最后,通过thenCombine将订单确认和支付验证的结果组合起来(虽然在这个场景中我们实际上并不关心它们的具体结果,只是用来等待它们全部完成),并使用join方法等待所有任务完成。

24.6 异常处理与调试

在异步编程中,异常处理是一个重要的方面。CompletableFuture通过exceptionally方法提供了一种优雅的异常处理机制。然而,当组合多个CompletableFuture时,异常的处理可能会变得复杂。因此,合理的异常处理策略和日志记录对于调试和维护异步程序至关重要。

此外,由于异步程序的行为难以预测,特别是在多线程环境下,因此进行充分的测试,特别是单元测试和集成测试,对于确保异步程序的正确性和稳定性至关重要。

24.7 总结

CompletableFuture作为Java并发编程中的一颗璀璨明珠,以其丰富的API和强大的功能,极大地简化了异步编程的复杂性。通过合理利用CompletableFuture的各种方法和组合机制,我们可以以声明性的方式编写出既高效又易于维护的异步代码。然而,正如任何强大的工具一样,CompletableFuture也需要我们谨慎使用,特别是在异常处理和调试方面。希望本章的内容能够帮助你更好地理解CompletableFuture,并在你的项目中灵活运用它,从而编写出更加高效、可靠的异步程序。