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. 基础使用

下面是一个简单的使用例子:

java 复制代码
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 构造函数

java 复制代码
public CompletableFuture() {
}

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

4.3.2 runAsync 方法

java 复制代码
public CompletableFuture<Void> runAsync(Runnable run) {
    // ... 省略部分代码 ...
    ForkJoinPool.commonPool().execute(new AsyncRun(run, null));
    return this;
}

runAsync 方法接受一个 Runnable 对象作为参数,并使用默认的 ForkJoinPool 异步执行它。这里创建了一个 AsyncRun 对象(它是 ForkJoinTask 的子类),并将其提交给线程池执行。

4.3.3 complete 方法

java 复制代码
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 方法

java 复制代码
private void propagateCompletion() {
    // ... 省略部分代码 ...
    for (CompletableFuture<?> c; (c = next) != null; next = c.next) {
        c.postComplete();
    }
    // ... 省略部分代码 ...
}

propagateCompletion 方法负责通知所有依赖当前 CompletableFuture 的其他 CompletableFuture 对象。这里通过遍历一个链表(next 字段指向下一个依赖的 CompletableFuture)来实现。

4.3.5 postComplete 方法

java 复制代码
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、大数据、数据结构算法(公众号同名

    ⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️欢迎关注下面的公众号:进朱者赤,认识不一样的技术人。⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️

相关推荐
阿伟*rui24 分钟前
配置管理,雪崩问题分析,sentinel的使用
java·spring boot·sentinel
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck2 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei2 小时前
java的类加载机制的学习
java·学习
Yaml44 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~4 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616884 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
aloha_7895 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
记录成长java5 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet
睡觉谁叫~~~5 小时前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust