Java8 CompletableFuture:异步编程的瑞士军刀

简介: Java8 CompletableFuture:异步编程的瑞士军刀

阿里非典型程序员一枚 ,记录平平无奇程序员在大厂的打怪升级之路。 一起学习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,允许在任务完成后启动新的异步操作 需确保处理函数返回CompletableFutureCompletionStage

请注意,这个表格只是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() {
}

构造函数非常简单,没有执行任何操作。实际上,状态的初始化和任务的执行是在后续的 runAsyncsupplyAsync 等方法中完成的。

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 中被调用,用于执行后续的动作,比如调用 thenApplythenAccept 等方法时提供的函数。

四、注意事项

  1. 异常处理:异步任务中抛出的异常不会被主线程捕获,除非使用.exceptionally()方法来处理。
  2. 线程池选择CompletableFuture默认使用ForkJoinPool,但在高并发场景下,可能需要自定义线程池以避免资源耗尽。
  3. 阻塞问题get()join()方法会阻塞当前线程,直到异步任务完成。在使用时要小心,避免造成死锁或性能问题。
  4. 取消操作CompletableFuture支持取消操作,但取消并不保证一定会成功。

五、总结

CompletableFuture是Java 8中引入的一个强大的异步编程工具,它提供了丰富的API和灵活的链式调用机制,使得编写异步代码变得简单而高效。通过合理使用CompletableFuture,我们可以编写出响应更快、吞吐量更高的并发程序。但同时也要注意其潜在的问题和限制,如异常处理、线程池选择等。

欢迎一键三连(关注+点赞+收藏),技术的路上一起加油!!!代码改变世界

  • 关于我:阿里非典型程序员一枚 ,记录平平无奇程序员在大厂的打怪升级之路。 一起学习Java、大数据、数据结构算法(公众号同名)
相关文章
|
1月前
|
Java 流计算
利用java8 的 CompletableFuture 优化 Flink 程序
本文探讨了Flink使用avatorscript脚本语言时遇到的性能瓶颈,并通过CompletableFuture优化代码,显著提升了Flink的QPS。文中详细介绍了avatorscript的使用方法,包括自定义函数、从Map中取值、使用Java工具类及AviatorScript函数等,帮助读者更好地理解和应用avatorscript。
利用java8 的 CompletableFuture 优化 Flink 程序
|
4月前
|
Java API 数据库
深研Java异步编程:CompletableFuture与反应式编程范式的融合实践
【7月更文挑战第1天】Java 8的CompletableFuture革新了异步编程,提供链式处理和优雅的错误处理。反应式编程,如Project Reactor,强调数据流和变化传播,擅长处理大规模并发和延迟敏感任务。两者结合,如通过Mono转换CompletableFuture,兼顾灵活性与资源管理,提升现代Java应用的并发性能和响应性。开发者可按需选择和融合这两种技术,以适应高并发环境。
54 1
|
5月前
|
Java API 数据库
深研Java异步编程:CompletableFuture与反应式编程范式的融合实践
【6月更文挑战第30天】Java 8的CompletableFuture革新了异步编程,提供如thenApply等流畅接口,而Java 9后的反应式编程(如Reactor)强调数据流和变化传播,以事件驱动应对高并发。两者并非竞争关系,而是互补,通过Flow API和第三方库结合,如将CompletableFuture转换为Mono进行反应式处理,实现更高效、响应式的系统设计。开发者可根据需求灵活选用,提升现代Java应用的并发性能。
73 1
|
5月前
|
存储 Java API
探索Java中的Lambda表达式:现代编程的瑞士军刀
随着Java 8的推出,Lambda表达式成为了Java编程语言的一大亮点。本篇文章旨在深入探讨Lambda表达式在Java中的应用及其对现代编程实践的影响。文章首先概述Lambda表达式的基本概念和语法结构,随后通过实例分析其在函数式编程接口中的运用,最后讨论Lambda表达式如何优化代码的可读性和简洁性,以及它对Java未来发展方向的潜在影响。
|
4月前
|
并行计算 Java API
Java中的Lambda表达式:现代编程的瑞士军刀
【7月更文挑战第28天】Lambda表达式,作为Java 8的一大亮点,为函数式编程范式在Java语言中的应用铺平了道路。它以其简洁、灵活的特性,成为解决日常编程问题的利器。本文将深入探讨Lambda表达式的基本概念、语法结构以及在实际开发中的应用案例,揭示如何利用这一特性提升代码的可读性和开发效率。
28 5
|
5月前
|
设计模式 Java API
实战分析Java的异步编程,并通过CompletableFuture进行高效调优
【6月更文挑战第7天】实战分析Java的异步编程,并通过CompletableFuture进行高效调优
90 2
|
4月前
|
并行计算 算法 Java
Java面试题:解释Java中的无锁编程的概念,Java中的Fork/Join框架的作用和使用场景,Java中的CompletableFuture的作用和使用场景
Java面试题:解释Java中的无锁编程的概念,Java中的Fork/Join框架的作用和使用场景,Java中的CompletableFuture的作用和使用场景
34 0
|
4月前
|
安全 Java 数据库连接
Java面试题:解释Java内存模型的无锁编程支持,并讨论其优势和局限性,解释Java中的CompletableFuture的工作原理,并讨论其在异步编程中的应用
Java面试题:解释Java内存模型的无锁编程支持,并讨论其优势和局限性,解释Java中的CompletableFuture的工作原理,并讨论其在异步编程中的应用
29 0
|
5月前
|
Java 开发者 Spring
Spring 框架:Java 企业应用开发的“瑞士军刀”,一网打尽所有需求!
【6月更文挑战第25天】Spring框架是Java开发的“瑞士军刀”,以其DI(依赖注入)减少手动管理,提高效率。AOP(面向切面编程)实现非侵入式关注点分离,如日志和事务管理。@Transactional注解简化事务处理,Web支持使Web应用开发更便捷。通过这些工具,Spring解决了复杂需求,增强了代码的可维护性和性能。
28 0
|
5月前
|
并行计算 Java API
Java8实战-CompletableFuture:组合式异步编程
Java8实战-CompletableFuture:组合式异步编程
62 0