阿里非典型程序员一枚 ,记录平平无奇程序员在大厂的打怪升级之路。 一起学习Java、大数据、数据结构算法(公众号同名)
引言
CompletableFuture
是Java 8的一个类,它简化了异步编程的复杂性,允许通过链式调用组合多个异步操作。在并发处理、网络IO和长时间计算任务中,它提高了应用的响应速度和性能,并提供了方便的异常处理机制。掌握CompletableFuture
对Java开发者来说非常有价值,能够提升编程能力和项目性能。
一、基础概览
CompletableFuture为Java开发者提供了一种强大而灵活的工具,用于处理异步操作和并发编程
2.1.CompletableFuture的功能
- 执行任务:CompletableFuture提供了runAsync()、supplyAsync()和thenRun()等方法用于执行任务。这些方法可以接受Runnable和Supplier接口的实例,用于定义任务的逻辑。其中,runAsync()方法用于执行没有返回值的任务,而supplyAsync()方法用于执行有返回值的任务。
- 结果处理:一旦任务完成,可以使用thenApply()、thenAccept()等方法对结果进行进一步处理。例如,thenApply()方法可以对任务的结果进行转换,并返回一个新的CompletableFuture。
- 异常处理:使用exceptionally()方法,可以捕获并处理异步任务执行过程中出现的异常。
- 组合操作:CompletableFuture还提供了allOf()和anyOf()等方法,用于组合多个异步任务。allOf()方法等待所有任务完成,而anyOf()方法则等待任何一个任务完成。
2.2. CompletableFuture的特点
- 异步执行:CompletableFuture支持异步执行,可以通过supplyAsync()或runAsync()方法提交任务到线程池中进行异步处理。这可以避免阻塞主线程,提高程序的响应性和吞吐量。
- 链式操作:通过thenApply()、thenAccept()、thenRun()等方法,可以将多个CompletableFuture串联起来形成一个操作链。这样,每个任务可以在前一个任务完成后自动执行下一个任务,从而实现流程的自动化。
- 组合操作:CompletableFuture提供了多种组合操作,如allOf()和anyOf(),用于处理多个CompletableFuture实例的结果。这使得开发者能够方便地处理并行任务,实现更复杂的业务逻辑。
- 异常处理:使用exceptionally()或handle()方法,可以捕获并处理异常情况,确保任务的稳定运行。这有助于减少程序中的错误和异常,提高程序的健壮性。
- 超时控制:使用completeOnTimeout()或completeOnCancel()方法,可以设置任务的超时时间。如果任务在指定的时间内未完成,可以执行相应的操作,避免无限期的等待。
2.3. CompletableFuture的使用场景
- 网络请求:在Web应用中,经常需要从远程服务器获取数据。使用CompletableFuture可以异步地发起网络请求,并在数据返回后自动处理结果,避免了同步阻塞等待。
- 数据计算:对于需要消耗较长时间的计算任务,可以使用CompletableFuture将其提交到线程池中异步执行。这样,主线程可以继续处理其他任务,而不会被长时间的计算所阻塞。
- 任务调度:当需要按照特定的顺序或并行关系执行多个任务时,可以使用CompletableFuture的组合操作来方便地实现。例如,可以等待多个任务全部完成后再执行后续操作,或者只需等待任何一个任务完成即可。
- 响应式编程:在响应式编程中,经常需要根据输入的数据流动态地生成输出。使用CompletableFuture可以方便地处理异步数据流,并在数据到达时自动触发相应的处理逻辑。
二、CompletableFuture详解
2.1. CompletableFuture技术细节
CompletableFuture实现了Future和CompletionStage接口
2.3. CompletableFuture和Future的对比
功能/特性 | CompletableFuture | Future |
异步执行 | 支持 | 支持 |
链式操作 | 支持,可以方便地构建任务依赖和结果转换 | 不支持 |
异常处理 | 提供丰富的异常处理方法,可灵活处理任务执行中的异常 | 有限,通常使用try-catch块捕获异常 |
回调执行 | 支持在任务完成时执行回调操作 | 不支持 |
组合和并行处理 | 支持多个任务的组合和并行处理 | 有限,不支持内置的操作符 |
手动完成 | 支持,可以在任务外部手动设置任务结果 | 不支持 |
线程池管理 | 可以使用自定义线程池执行任务 | 通常依赖默认线程池或手动管理 |
从表格中可以看出,CompletableFuture在链式操作、异常处理、回调执行、组合和并行处理等方面提供了更强大的功能,而Future在这些方面相对有限。此外,CompletableFuture还支持手动完成任务,提供了更灵活的任务管理方式。因此,在需要更高级别的异步编程和并发控制时,CompletableFuture是更好的选择。然而,如果只是简单的异步任务执行,并且不需要复杂的链式操作或异常处理,那么Future可能是一个更简单的解决方案。
三、使用方法
3.1. 基础使用
下面是一个简单的使用例子:
import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class CompletableFutureExample { public static void main(String[] args) throws ExecutionException, InterruptedException { // 创建一个异步任务 CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { // 模拟一个耗时操作 try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IllegalStateException(e); } return "Hello, CompletableFuture!"; }); // 当任务完成后,获取结果 System.out.println(future.get()); // 输出: Hello, CompletableFuture! } } •
在上面的例子中,CompletableFuture.supplyAsync
方法接受一个Supplier
,异步地执行该Supplier
中的代码,并返回一个CompletableFuture
对象。调用get()
方法会阻塞当前线程,直到异步任务完成并返回结果。
3.2. 方法列表
以下是CompletableFuture
的常用和非常用方法列表,以表格形式展示:
3.2.1. 常用方法
下面是将CompletableFuture
的常用和非常用方法以表格形式进行区分的示例:
方法分类 | 方法名 | 返回类型 | 功能描述 | 注意事项 |
获取结果 | get() |
T |
阻塞等待并获取异步任务的结果 | 可能导致线程阻塞,需处理ExecutionException |
get(long timeout, TimeUnit unit) |
T |
在指定时间内阻塞等待并获取结果 | 可能抛出TimeoutException |
|
异常处理 | exceptionally(Function<Throwable, ? extends T> fn) |
CompletableFuture<T> |
提供异常处理逻辑并返回新的CompletableFuture |
需确保异常处理函数不抛出异常 |
组合任务 | thenAccept(Consumer<? super T> action) |
CompletableFuture<Void> |
在任务完成后执行消费操作 | 需确保消费函数不抛出异常 |
thenApply(Function<? super T, ? extends U> fn) |
CompletableFuture<U> |
在任务完成后转换结果为新的CompletableFuture |
需确保处理函数不抛出异常 | |
thenRun(Runnable action) |
CompletableFuture<Void> |
在任务完成后执行无参数的运行操作 | 需确保运行函数不抛出异常 |
3.2.2. 非常用方法
方法分类 | 方法名 | 返回类型 | 功能描述 | 注意事项 |
组合任务 | allOf(CompletableFuture<?>... cfs) |
CompletableFuture<Void> |
等待所有任务完成,返回表示完成的CompletableFuture |
不返回任务结果,需单独处理每个任务 |
anyOf(CompletableFuture<?>... cfs) |
CompletableFuture<Object> |
等待任意一个任务完成,返回第一个完成的CompletableFuture |
需处理结果类型转换和异常 | |
异步执行 | supplyAsync(Supplier<T> supplier) |
CompletableFuture<T> |
异步执行并返回结果的CompletableFuture |
需指定执行器或默认使用ForkJoinPool.commonPool() |
runAsync(Runnable runnable) |
CompletableFuture<Void> |
异步执行无返回值的任务 | 需指定执行器或默认使用ForkJoinPool.commonPool() |
|
转换类型 | completedFuture(T value) |
CompletableFuture<T> |
返回一个已完成的CompletableFuture ,其结果为给定值 |
用于立即返回已知结果的异步操作 |
转换操作 | thenCompose(Function<? super T, ? extends CompletionStage<U>> fn) |
CompletableFuture<U> |
链式组合CompletableFuture ,允许在任务完成后启动新的异步操作 |
需确保处理函数返回CompletableFuture 或CompletionStage |
请注意,这个表格只是CompletableFuture
方法的一个概览,并且可能并不包含所有可用的方法。在实际使用中,还应参考Java官方文档以获取最准确和完整的信息。此外,即使是“非常用”的方法,在特定的应用场景下也可能非常有用,因此建议了解并熟悉它们以便在需要时使用。
四、内部原理与实现细节
CompletableFuture
是 Java 8 引入的一个用于异步编程的类,它内部实现了一套复杂的机制来管理异步任务的执行、结果的传递以及异常的处理。下面我们将深入探讨 CompletableFuture
的内部原理,并解读其部分源码,以理解其如何工作。
4.1. 核心组件
CompletableFuture
的核心组件主要包括:
- 状态管理:使用
volatile
修饰的整型变量state
来表示 CompletableFuture 的状态(如未完成、正常完成、异常完成等)。 - 结果存储:使用
volatile
修饰的Object
类型变量result
来存储异步任务的结果或异常。 - 依赖管理:
CompletableFuture
内部可能维护多个依赖它的CompletableFuture
,当自身状态变化时,需要通知这些依赖进行相应的处理。 - 线程池:
CompletableFuture
默认使用ForkJoinPool.commonPool()
作为执行异步任务的线程池,但也可以自定义线程池。
4.2. 状态流转
CompletableFuture
的状态流转是其内部机制的关键部分。状态通常包括以下几种:
- NEW:初始状态,表示任务还未开始。
- COMPLETING:任务正在完成中,此时不允许其他线程修改状态。
- NORMAL:任务正常完成。
- EXCEPTIONAL:任务异常完成。
- CANCELLED:任务被取消。
状态的流转通过 CAS(Compare-and-Swap)操作来保证线程安全。
4. 3. 源码解读
下面选取 CompletableFuture
的部分源码进行解读:
4.3.1 构造函数
public CompletableFuture() { }
构造函数非常简单,没有执行任何操作。实际上,状态的初始化和任务的执行是在后续的 runAsync
、supplyAsync
等方法中完成的。
4.3.2 runAsync 方法
public CompletableFuture<Void> runAsync(Runnable run) { // ... 省略部分代码 ... ForkJoinPool.commonPool().execute(new AsyncRun(run, null)); return this; }
runAsync
方法接受一个 Runnable
对象作为参数,并使用默认的 ForkJoinPool
异步执行它。这里创建了一个 AsyncRun
对象(它是 ForkJoinTask
的子类),并将其提交给线程池执行。
4.3.3 complete 方法
public boolean complete(T value) { return completeValue(value); } private boolean completeValue(T value) { if (completeValueInternal(value)) { propagateCompletion(); return true; } return false; }
complete
方法用于在正常情况下完成 CompletableFuture
并设置其结果。它首先调用 completeValueInternal
方法来尝试设置结果,如果成功,则调用 propagateCompletion
方法来通知所有依赖的 CompletableFuture
。
4.3.4 propagateCompletion 方法
private void propagateCompletion() { // ... 省略部分代码 ... for (CompletableFuture<?> c; (c = next) != null; next = c.next) { c.postComplete(); } // ... 省略部分代码 ... }
propagateCompletion
方法负责通知所有依赖当前 CompletableFuture
的其他 CompletableFuture
对象。这里通过遍历一个链表(next
字段指向下一个依赖的 CompletableFuture
)来实现。
4.3.5 postComplete 方法
final void postComplete() { // ... 省略部分代码 ... try { postAction(); } finally { // ... 省略部分代码 ... } }
postComplete
方法在 propagateCompletion
中被调用,用于执行后续的动作,比如调用 thenApply
、thenAccept
等方法时提供的函数。
四、注意事项
- 异常处理:异步任务中抛出的异常不会被主线程捕获,除非使用
.exceptionally()
方法来处理。 - 线程池选择:
CompletableFuture
默认使用ForkJoinPool,但在高并发场景下,可能需要自定义线程池以避免资源耗尽。 - 阻塞问题:
get()
和join()
方法会阻塞当前线程,直到异步任务完成。在使用时要小心,避免造成死锁或性能问题。 - 取消操作:
CompletableFuture
支持取消操作,但取消并不保证一定会成功。
五、总结
CompletableFuture
是Java 8中引入的一个强大的异步编程工具,它提供了丰富的API和灵活的链式调用机制,使得编写异步代码变得简单而高效。通过合理使用CompletableFuture
,我们可以编写出响应更快、吞吐量更高的并发程序。但同时也要注意其潜在的问题和限制,如异常处理、线程池选择等。
欢迎一键三连(关注+点赞+收藏),技术的路上一起加油!!!代码改变世界
- 关于我:阿里非典型程序员一枚 ,记录平平无奇程序员在大厂的打怪升级之路。 一起学习Java、大数据、数据结构算法(公众号同名)