首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
01 | 可见性、原子性和有序性问题:并发编程Bug的源头
02 | Java内存模型:看Java如何解决可见性和有序性问题
03 | 互斥锁(上):解决原子性问题
04 | 互斥锁(下):如何用一把锁保护多个资源?
05 | 一不小心就死锁了,怎么办?
06 | 用“等待-通知”机制优化循环等待
07 | 安全性、活跃性以及性能问题
08 | 管程:并发编程的万能钥匙
09 | Java线程(上):Java线程的生命周期
10 | Java线程(中):创建多少线程才是合适的?
11 | Java线程(下):为什么局部变量是线程安全的?
12 | 如何用面向对象思想写好并发程序?
13 | 理论基础模块热点问题答疑
14 | Lock和Condition(上):隐藏在并发包中的管程
15 | Lock和Condition(下):Dubbo如何用管程实现异步转同步?
16 | Semaphore:如何快速实现一个限流器?
17 | ReadWriteLock:如何快速实现一个完备的缓存?
18 | StampedLock:有没有比读写锁更快的锁?
19 | CountDownLatch和CyclicBarrier:如何让多线程步调一致?
20 | 并发容器:都有哪些“坑”需要我们填?
21 | 原子类:无锁工具类的典范
22 | Executor与线程池:如何创建正确的线程池?
23 | Future:如何用多线程实现最优的“烧水泡茶”程序?
24 | CompletableFuture:异步编程没那么难
25 | CompletionService:如何批量执行异步任务?
26 | Fork/Join:单机版的MapReduce
27 | 并发工具类模块热点问题答疑
28 | Immutability模式:如何利用不变性解决并发问题?
29 | Copy-on-Write模式:不是延时策略的COW
30 | 线程本地存储模式:没有共享,就没有伤害
31 | Guarded Suspension模式:等待唤醒机制的规范实现
32 | Balking模式:再谈线程安全的单例模式
33 | Thread-Per-Message模式:最简单实用的分工方法
34 | Worker Thread模式:如何避免重复创建线程?
35 | 两阶段终止模式:如何优雅地终止线程?
36 | 生产者-消费者模式:用流水线思想提高效率
37 | 设计模式模块热点问题答疑
38 | 案例分析(一):高性能限流器Guava RateLimiter
39 | 案例分析(二):高性能网络应用框架Netty
40 | 案例分析(三):高性能队列Disruptor
41 | 案例分析(四):高性能数据库连接池HiKariCP
42 | Actor模型:面向对象原生的并发模型
43 | 软件事务内存:借鉴数据库的并发经验
44 | 协程:更轻量级的线程
45 | CSP模型:Golang的主力队员
当前位置:
首页>>
技术小册>>
Java并发编程实战
小册名称:Java并发编程实战
### 24 | CompletableFuture:异步编程没那么难 在Java的并发编程领域,随着应用程序对性能要求的不断提高,异步编程逐渐成为处理复杂任务、提升程序响应性和吞吐量的重要手段。然而,传统的异步编程模式,如使用`Future`接口,虽然能够实现异步执行,但在处理复杂逻辑、组合多个异步任务以及异常处理方面显得力不从心。Java 8引入的`CompletableFuture`类,正是为了解决这些问题而设计的,它极大地简化了异步编程的复杂性,让开发者能够以更直观、更灵活的方式编写异步代码。 #### 24.1 初探CompletableFuture `CompletableFuture`是`Future`接口的一个增强版,它实现了`Future`和`CompletionStage`两个接口。与`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<Throwable,? extends T> 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`来优雅地实现这一过程。 ```java CompletableFuture<Void> checkInventory = CompletableFuture.runAsync(() -> { // 异步检查库存 System.out.println("Checking inventory..."); // 模拟耗时操作 try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("Inventory checked."); }); CompletableFuture<Void> verifyPayment = CompletableFuture.runAsync(() -> { // 异步验证支付 System.out.println("Verifying payment..."); // 模拟耗时操作 try { Thread.sleep(1500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("Payment verified."); }); CompletableFuture<Void> confirmOrder = checkInventory.thenRun(() -> { // 库存检查通过后,确认订单 System.out.println("Confirming order..."); // 模拟耗时操作 try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("Order confirmed."); }); CompletableFuture<Void> allDone = confirmOrder.thenCombine(verifyPayment, (unused1, unused2) -> null); allDone.join(); // 等待所有任务完成 System.out.println("Order processing completed."); ``` 在这个例子中,我们创建了两个异步任务`checkInventory`和`verifyPayment`,分别用于检查库存和验证支付。然后,我们使用`thenRun`在库存检查完成后确认订单。最后,通过`thenCombine`将订单确认和支付验证的结果组合起来(虽然在这个场景中我们实际上并不关心它们的具体结果,只是用来等待它们全部完成),并使用`join`方法等待所有任务完成。 #### 24.6 异常处理与调试 在异步编程中,异常处理是一个重要的方面。`CompletableFuture`通过`exceptionally`方法提供了一种优雅的异常处理机制。然而,当组合多个`CompletableFuture`时,异常的处理可能会变得复杂。因此,合理的异常处理策略和日志记录对于调试和维护异步程序至关重要。 此外,由于异步程序的行为难以预测,特别是在多线程环境下,因此进行充分的测试,特别是单元测试和集成测试,对于确保异步程序的正确性和稳定性至关重要。 #### 24.7 总结 `CompletableFuture`作为Java并发编程中的一颗璀璨明珠,以其丰富的API和强大的功能,极大地简化了异步编程的复杂性。通过合理利用`CompletableFuture`的各种方法和组合机制,我们可以以声明性的方式编写出既高效又易于维护的异步代码。然而,正如任何强大的工具一样,`CompletableFuture`也需要我们谨慎使用,特别是在异常处理和调试方面。希望本章的内容能够帮助你更好地理解`CompletableFuture`,并在你的项目中灵活运用它,从而编写出更加高效、可靠的异步程序。
上一篇:
23 | Future:如何用多线程实现最优的“烧水泡茶”程序?
下一篇:
25 | CompletionService:如何批量执行异步任务?
该分类下的相关小册推荐:
Java必知必会-JDBC
Java语言基础9-常用API和常见算法
Java语言基础3-流程控制
Java语言基础4-数组详解
手把手带你学习SpringBoot-零基础到实战
经典设计模式Java版
Java语言基础8-Java多线程
Java语言基础12-网络编程
Java面试指南
Mybatis合辑4-Mybatis缓存机制
Java语言基础14-枚举和注解
Java语言基础10-Java中的集合