一万两千字,一文讲懂如何使用​​CompletableFuture_线程池

CompletableFuture 是 Java 8 引入的一个类,用于简化异步编程模型。它是 Future 接口的一个增强版本,提供了更加丰富的功能和更灵活的用法。CompletableFuture 允许开发者以一种声明式和链式的方式编写异步代码,这样可以提高代码的可读性和可维护性。

CompletableFuture的定义

一万两千字,一文讲懂如何使用​​CompletableFuture_异步操作_02

CompletableFuture它同时实现了FutureCompletionStage两个接口。

  • FutureFuture接口代表了一个异步计算的结果,也就是说,它代表了某个未来可能完成的计算结果。Future接口提供了获取结果、检查是否完成、取消任务等方法。
  • CompletionStageCompletionStage接口则代表了一个异步计算的阶段,或者说,它代表了一个异步计算过程的某一步(Stage)。这一步可能是由另一个CompletionStage触发的,也可能是在当前步骤完成后触发的。这就意味着,我们可以利用CompletionStage接口来编排和组合多个异步计算步骤,从而实现复杂的异步编程逻辑。

为什么要使用 CompletableFuture

  1. 简化异步编程:在传统的异步编程中,处理线程间的通信和数据传递通常需要复杂的同步机制,如 wait/notifyCountDownLatchCyclicBarrier 等。CompletableFuture 提供了一系列的方法,如 thenApplythenAcceptthenRun 等,使得异步操作的编排变得更加简单直观。
  2. 支持非阻塞操作CompletableFuture 支持非阻塞的异步编程模式。开发者可以在不阻塞当前线程的情况下,安排一个异步操作,并在操作完成时接收通知。这种方式可以提高应用程序的响应性和吞吐量。
  3. 丰富的组合操作CompletableFuture 提供了多种方法来组合多个异步操作,例如 thenCombinethenComposeallOfanyOf 等。这些方法可以帮助开发者轻松实现复杂的异步流程控制。
  4. 异常处理CompletableFuture 允许开发者通过 exceptionallyhandle 方法来处理异步操作中发生的异常,这样可以避免异常导致的线程中断和应用程序崩溃。
  5. 支持取消操作CompletableFuture 支持取消异步操作。如果某个异步任务不再需要,可以通过 cancel 方法来取消它,从而节省系统资源。
  6. 更好的并发控制CompletableFuture 可以与 Java 的其他并发工具(如 ExecutorService)无缝集成,提供了更好的并发控制能力。

CompletableFuture使用场景

  • IO密集型操作:如数据库操作、文件读写、网络请求等,可以使用 CompletableFuture 来异步执行,避免阻塞主线程。
  • 计算密集型操作:如果计算可以在不同的线程中并行执行,使用 CompletableFuture 可以提高计算效率。
  • 异步编程模式:在需要处理多个异步操作的结果,并且这些操作之间存在依赖关系时,CompletableFuture 提供了一种优雅的解决方案。

CompletableFuture如何使用

零依赖:CompletableFuture的创建

CompletableFuture的创建方式有很多中,下面将逐一进行介绍。

使用 new CompletableFuture() 构造函数

可以使用new CompletableFuture()构造函数来创建一个新的CompletableFuture,这时CompletableFuture还没有任何结果,你需要手动调用complete方法来设置CompletableFuture来完成这个CompletableFuture。例如,你希望创建一个CompletableFuture,然后将其交给某个异步任务去完成。

CompletableFuture<String> future = new CompletableFuture<>();
// 现在future是未完成的
// 可以通过future.complete("result")来设置结果
// 或者通过future.completeExceptionally(new Exception())来设置异常
使用completedFuture()方法

completedFuture()方法是CompletableFuture类的一个静态方法,它允许你立即创建一个已经完成的CompletableFuture对象。这个方法通常用于创建一个已经包含结果的CompletableFuture,而不需要等待异步任务完成。

// 创建一个已经完成的CompletableFuture
CompletableFuture<String> future = CompletableFuture.completedFuture("Hello");

// 获取结果
String result = future.get();
System.out.println(result); // 输出:Hello
使用supplyAsync()方法

supplyAsync()CompletableFuture类的一个静态方法,它允许你创建一个新的CompletableFuture,这个CompletableFuture会在后台线程中执行一个任务,并在任务完成后返回结果。

supplyAsync()方法有两个重载版本,它们的区别在于是否提供了一个Executor参数。

  1. supplyAsync(Supplier<T> supplier):这个版本的supplyAsync()方法会在一个由系统管理的线程池中执行任务。这意味着你无法控制任务的执行环境,例如线程池的大小或者线程的优先级。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");
  1. supplyAsync(Supplier<T> supplier, Executor executor):这个版本的supplyAsync()方法允许你提供一个自定义的Executor,这样你就可以控制任务的执行环境。
Executor executor = Executors.newSingleThreadExecutor();
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello", executor);
使用runAsync()方法

runAsync()方法是CompletableFuture类的另一个静态方法,它允许你创建一个新的CompletableFuture,这个CompletableFuture会在后台线程中执行一个任务,但是这个任务不会有任何返回值。也就是说,runAsync()方法主要用于执行一些没有返回值的任务,比如发送邮件、下载文件等等。

runAsync()方法也有两个重载版本,它们的区别同样在于是否提供了一个Executor参数。

  1. runAsync(Runnable runnable):这个版本的runAsync()方法会在一个由系统管理的线程池中执行任务。同样地,你无法控制任务的执行环境,例如线程池的大小或者线程的优先级。
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    // 这里写你的任务代码
});
  1. runAsync(Runnable runnable, Executor executor):这个版本的runAsync()方法允许你提供一个自定义的Executor,这样你就可以控制任务的执行环境。
Executor executor = Executors.newSingleThreadExecutor();
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    // 这里写你的任务代码
}, executor);
CompletableFuture创建方式总结

一万两千字,一文讲懂如何使用​​CompletableFuture_异步操作_03

为什么要用自定义的线程池

使用自定义的线程池创建异步任务,主要原因是为了更好地控制和管理线程资源。以下是使用自定义线程池的一些优点:

  1. 更好的资源管理:自定义线程池可以根据应用的需求来调整线程的数量和类型,以满足不同的性能需求。例如,如果你的应用主要是I/O密集型的,那么你可能需要一个较大的线程池;如果你的应用主要是CPU密集型的,那么你可能需要一个较小的线程池。
  2. 避免过度创建线程:如果没有使用线程池,那么每次创建一个新线程都会消耗一定的系统资源。而使用线程池后,系统只需要创建一定数量的线程,然后在多个任务之间复用这些线程,从而避免了过度创建线程的问题。
  3. 提高系统稳定性:自定义线程池可以设置合适的线程数量和线程类型,从而提高系统的稳定性和可靠性。例如,你可以设置线程池的最大线程数为系统的可用内存除以线程的大小,这样可以防止系统因为过多的线程而导致内存溢出。

相比之下,使用默认的线程池有一些潜在的问题:

  1. 不适合I/O密集型任务:默认的线程池通常是针对CPU密集型任务设计的,对于I/O密集型任务可能会导致线程资源的浪费。当不传递Executor时,会使用ForkJoinPool中的共用线程池CommonPoolCommonPool的大小是CPU核数-1,如果是IO密集的应用,线程数可能成为瓶颈。
  2. 可能导致线程资源不足:如果系统中同时运行的任务太多,那么默认的线程池可能会导致线程资源不足,从而影响系统的性能。
  3. 难以进行优化:由于默认的线程池是由系统管理的,所以用户很难对其进行优化。例如,用户不能根据具体的任务需求来调整线程池的大小和类型。

因此,为了更好地控制和管理线程资源,通常建议使用自定义的线程池来创建异步任务。

CompletableFuture 获取返回结果

CompletableFuture 它提供了多种方法来处理异步操作的结果。以下是 CompletableFutureget()join()isDone() 方法的详细说明

get()的具体使用方法

get() 方法用于获取 CompletableFuture 的结果。如果异步操作尚未完成,get() 方法会阻塞当前线程直到操作完成。如果异步操作失败,get() 方法会抛出 ExecutionException 来包装原始的异常。如果当前线程在等待期间被中断,它会抛出 InterruptedException

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 执行一些异步操作
    return "Result";
});

try {
    String result = future.get(); // 阻塞直到 future 完成
    System.out.println(result);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt(); // 重置中断状态
    // 处理中断异常
} catch (ExecutionException e) {
    // 处理异步操作中的异常
}
join()的具体使用方法

join() 方法与 get() 方法类似,也用于获取 CompletableFuture 的结果。不同之处在于,join() 不会抛出 InterruptedException。如果当前线程在等待期间被中断,join() 会将中断状态设置回线程,但不抛出异常。join() 可以视为 get() 的非抛出中断版本的替代方法。

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    // 执行一些异步操作
    System.out.println("Running some async operation...");
});

future.join(); // 阻塞直到 future 完成
isDone()的具体使用方法

isDone() 方法用于检查 CompletableFuture 是否已经完成。它返回一个布尔值,如果异步操作已经完成,则返回 true;否则返回 false。这个方法是非阻塞的,可以用来轮询异步操作的完成状态。

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    // 执行一些异步操作
    System.out.println("Running some async operation...");
});

boolean isCompleted = future.isDone(); // 检查 future 是否完成
if (isCompleted) {
    // 处理已完成的操作
    System.out.println("Async operation completed.");
} else {
    // 处理未完成的操作
    System.out.println("Async operation is still running.");
}
使用方式的区别
  • get()join() 都会等待异步操作完成并返回结果,但 get() 在等待期间线程被中断时会抛出异常,而 join() 不会。
  • isDone() 用于检查异步操作是否完成,而不等待操作完成,适用于非阻塞的轮询场景。

在实际使用中,选择哪个方法取决于你的具体需求,例如是否需要处理中断、是否需要立即结果以及是否希望阻塞当前线程。

一元依赖:任务的回调
thenRun/thenRunAsync方法

CompletableFuturethenRunthenRunAsync 方法用于在异步操作完成后执行一个动作,这个动作由一个 Runnable 对象表示。这两个方法都用于在异步操作链中添加一个不返回值的操作,但它们在执行方式上有所不同。

  • 区别thenRun 是在 CompletableFuture 默认的执行器上执行 Runnable,而 thenRunAsync 允许你指定一个自定义的 Executor 来异步执行 Runnable
  • 返回值:两者都返回 CompletableFuture<Void> 类型,表示它们不返回任何有意义的值,即它们的操作不产生结果,通常用于执行副作用。
  • 自定义线程池thenRun 使用默认的 ForkJoinPool.commonPool(),而 thenRunAsync 可以使用自定义的线程池。
  • 执行方式thenRun 是同步执行,而 thenRunAsync 是异步执行。
thenRun 的具体使用方法

thenRun 方法在前一个 CompletableFuture 完成之后,会在默认的执行器上执行提供的 Runnable

CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> "Hello")
    .thenRun(() -> System.out.println("World")); // 异步执行,打印 "World"

在这个例子中,我们异步地供应字符串 "Hello",一旦这个操作完成,就会在默认的执行器上执行 Runnable,打印出 "World"。

thenRunAsync 的具体使用方法

thenRunAsync 方法在前一个 CompletableFuture 完成之后,会使用指定的 Executor 异步执行提供的 Runnable

ExecutorService executor = Executors.newFixedThreadPool(3); // 创建自定义的线程池

CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> "Hello")
    .thenRunAsync(() -> System.out.println("World"), executor); // 使用自定义线程池执行

// 注意:在使用完自定义线程池后,应该关闭它以释放资源
executor.shutdown();

在这个例子中,我们创建了一个自定义的线程池,并在异步供应 "Hello" 之后,使用 thenRunAsync 来执行一个任务,该任务在指定的线程池中打印 "World"。

thenApply/thenApplyAsync方法

CompletableFuturethenApplythenApplyAsync 方法用于在异步操作完成后,对结果应用一个函数,并将函数的返回值封装进一个新的 CompletableFuture 中。这两个方法允许对异步操作的结果进行转换或处理。

  • 区别thenApply 使用 CompletableFuture 的默认执行器来同步执行提供的函数,而 thenApplyAsync 明确地异步执行函数,并且可以指定自定义的 ExecutorService
  • 返回值:两者都会返回一个新的 CompletableFuture,其类型是应用函数后的结果类型。
  • 自定义线程池thenApply 使用默认的 ForkJoinPool.commonPool(),而 thenApplyAsync 可以使用自定义的 ExecutorService
  • 执行方式thenApply 是同步执行,thenApplyAsync 是异步执行。
thenApply 的具体使用方法

thenApply 方法在前一个 CompletableFuture 完成之后,会同步执行提供的函数。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
    .thenApply(s -> s + " World"); // 同步执行,将结果转换为 "Hello World"

System.out.println(future.join()); // 输出 "Hello World"

在这个例子中,我们异步地供应字符串 "Hello",一旦这个操作完成,就会同步执行 thenApply 中的函数,将结果转换为 "Hello World"。

thenApplyAsync 的具体使用方法

thenApplyAsync 方法在前一个 CompletableFuture 完成之后,会使用指定的 ExecutorService 异步执行提供的函数。

ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建自定义的线程池

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
    .thenApplyAsync(s -> s + " World", executorService); // 使用自定义线程池异步执行

System.out.println(future.join()); // 输出 "Hello World"

// 注意:在使用完自定义线程池后,应该关闭它以释放资源
executorService.shutdown();

在这个例子中,我们创建了一个自定义的线程池,并在异步供应 "Hello" 之后,使用 thenApplyAsync 来异步地执行一个任务,该任务将结果转换为 "Hello World"。

thenCompose/thenComposeAsync方法

CompletableFuturethenComposethenComposeAsync 方法用于在异步操作完成后,将结果传递给另一个异步操作。这两个方法允许你将多个异步操作链接起来,形成链式调用。

  • 区别thenCompose 是同步地将结果传递给另一个 CompletableFuture,而 thenComposeAsync 是异步地进行传递,并且可以指定自定义的线程池。
  • 返回值:两者都会返回一个新的 CompletableFuture,其类型是传递给下一个异步操作的函数返回值的类型。
  • 自定义线程池thenCompose 使用默认的执行器,而 thenComposeAsync 允许你使用自定义的线程池。
  • 执行方式thenCompose 是同步执行,thenComposeAsync 是异步执行。
thenCompose 的具体使用方法

thenCompose 方法接受一个函数,该函数返回另一个 CompletableFuture。当当前 CompletableFuture 完成时,会立即使用它作为参数,调用提供的函数,并继续链式操作。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
    .thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " World"));
System.out.println(future.join());

在这个例子中,我们异步地供应字符串 "Hello",一旦这个操作完成,就会立即执行 thenCompose 中的函数,异步地供应 "Hello World"。

thenComposeAsync 的具体使用方法

thenComposeAsync 方法同样接受一个返回 CompletableFuture 的函数,但它明确地异步执行这个函数,并允许你指定一个自定义的 Executor

ExecutorService executor = Executors.newFixedThreadPool(3); // 创建自定义的线程池

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
        .thenComposeAsync(s -> CompletableFuture.supplyAsync(() -> s + " World"), executor);

System.out.println(future.join());
//在使用完自定义线程池后,应该关闭它以释放资源
executor.shutdown();

在这个例子中,我们创建了一个自定义的线程池,并在异步供应 "Hello" 之后,使用 thenComposeAsync 来异步地执行一个任务,该任务异步地供应 "Hello World"。

thenAccept/thenAcceptAsync方法

CompletableFuturethenAcceptthenAcceptAsync 方法用于在异步操作完成后对结果进行处理。它们都接受一个 Consumer 对象作为参数,该对象代表要对异步操作结果执行的操作。这两个方法的主要区别在于它们的执行方式和是否允许使用自定义线程池。

  • 区别thenAccept 默认在 CompletableFuture 的默认执行器上同步执行 Consumer,而 thenAcceptAsync 明确地异步执行 Consumer,并允许指定自定义的 ExecutorService
  • 返回值:两者都返回 CompletableFuture<Void>,表示它们不返回计算结果,即它们的操作不产生值。
  • 自定义线程池thenAccept 使用默认的 ForkJoinPool.commonPool(),而 thenAcceptAsync 可以使用自定义的 ExecutorService
  • 执行方式thenAccept 是同步执行,thenAcceptAsync 是异步执行。
thenAccept 的具体使用方法

thenAccept 方法在前一个 CompletableFuture 完成之后,会在默认的执行器上同步执行提供的 Consumer

CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> "Hello")
                .thenAccept(s -> System.out.println(s + " World")); // 同步执行,打印 "Hello World"

在这个例子中,我们异步地供应字符串 "Hello",一旦这个操作完成,就会在默认的执行器上同步执行 Consumer,打印出 "Hello World"。

thenAcceptAsync 的具体使用方法

thenAcceptAsync 方法在前一个 CompletableFuture 完成之后,会使用指定的 ExecutorService 异步执行提供的 Consumer

ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建自定义的线程池

CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> "Hello")
        .thenAcceptAsync(s -> System.out.println(s + " World"), executorService); // 使用自定义线程池执行

System.out.println(future);
//在使用完自定义线程池后,应该关闭它以释放资源
executorService.shutdown();

在这个例子中,我们创建了一个自定义的线程池,并在异步供应 "Hello" 之后,使用 thenAcceptAsync 来执行一个任务,该任务在指定的线程池中打印 "Hello World"。

whenComplete/whenCompleteAsync方法

CompletableFuturewhenCompletewhenCompleteAsync 方法用于在异步操作完成后执行一个操作,无论操作成功还是异常。这两个方法都接受一个 BiConsumer<? super T, ? super Throwable> 函数式接口的实现作为参数,该接口允许你访问异步操作的结果或者异常。这两个方法的主要区别在于它们的执行方式。

  • 区别whenComplete 默认在 CompletableFuture 的默认执行器上同步执行 BiConsumer,而 whenCompleteAsync 明确地异步执行 BiConsumer,并允许指定自定义的 ExecutorService
  • 返回值:两者都返回 CompletableFuture<T>,即它们返回的是原始 CompletableFuture 的结果类型 T
  • 自定义线程池whenComplete 使用默认的 ForkJoinPool.commonPool(),而 whenCompleteAsync 可以使用自定义的 ExecutorService
  • 执行方式whenComplete 是同步执行,whenCompleteAsync 是异步执行。
whenComplete 的具体使用方法

whenComplete 方法在 CompletableFuture 完成之后,无论是正常完成还是有异常,都会在默认的执行器上同步执行提供的 BiConsumer

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
        .whenComplete((s, throwable) -> {
            if (throwable != null) {
                System.out.println("Error: " + throwable.getMessage());
            } else {
                System.out.println("Result: " + s);
            }
        });

// 可以链式调用 thenApply 来处理结果
CompletableFuture<String> finalFuture = future.thenApply(s -> s + " World");
System.out.println(finalFuture.join()); // 输出 "Hello World"

在这个例子中,我们异步地供应字符串 "Hello",一旦这个操作完成,就会在默认的执行器上同步执行 BiConsumer,根据操作的结果或异常打印相应的信息。

whenCompleteAsync 的具体使用方法

whenCompleteAsync 方法在 CompletableFuture 完成之后,无论是正常完成还是有异常,都会使用指定的 ExecutorService 异步执行提供的 BiConsumer

ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建自定义的线程池

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
        .whenCompleteAsync((s, throwable) -> {
            if (throwable != null) {
                System.out.println("Error: " + throwable.getMessage());
            } else {
                System.out.println("Result: " + s);
            }
        }, executorService); // 使用自定义线程池执行

System.out.println(future);
// 注意:在使用完自定义线程池后,应该关闭它以释放资源
executorService.shutdown();

在这个例子中,我们创建了一个自定义的线程池,并在异步供应 "Hello" 之后,使用 whenCompleteAsync 来异步执行一个任务,该任务根据操作的结果或异常打印相应的信息。

handle/handleAsync方法

CompletableFuturehandlehandleAsync 方法用于在异步操作完成后执行一个操作,并且这个操作可以处理异步操作的结果或者异常。这两个方法都接受一个 BiFunction<? super T, Throwable, ? extends U> 函数式接口的实现作为参数,该接口允许你访问异步操作的结果或者捕获的异常,并返回一个新的值。

  • 区别handleCompletableFuture 的默认执行器上同步执行 BiFunction,而 handleAsync 明确地异步执行 BiFunction,并允许指定自定义的 ExecutorService
  • 返回值:两者都返回 CompletableFuture<U>,即它们返回一个新的 CompletableFuture 实例,其类型 U 是由 handlehandleAsync 方法中的函数返回的。
  • 自定义线程池handle 使用默认的 ForkJoinPool.commonPool(),而 handleAsync 可以使用自定义的 ExecutorService
  • 执行方式handle 是同步执行,handleAsync 是异步执行。
handle 的具体使用方法

handle 方法在 CompletableFuture 完成之后,无论是正常完成还是有异常,都会在默认的执行器上同步执行提供的 BiFunction

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
    .handle((s, throwable) -> {
        if (throwable != null) {
            System.out.println("Error occurred: " + throwable.getMessage());
            return "Error";
        } else {
            System.out.println("Result: " + s);
            return s + " World";
        }
    });

// 可以链式调用其他方法来继续处理返回的值
CompletableFuture<String> finalFuture = future.thenApply(result -> "Final: " + result);
// 根据前面的handle方法,可能输出 "Final: Hello World"
System.out.println(finalFuture.join());
handleAsync 的具体使用方法

handleAsync 方法在 CompletableFuture 完成之后,无论是正常完成还是有异常,都会使用指定的 ExecutorService 异步执行提供的 BiFunction

ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建自定义的线程池

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
        .handleAsync((s, throwable) -> {
            if (throwable != null) {
                System.out.println("Error occurred: " + throwable.getMessage());
                return "Error";
            } else {
                System.out.println("Result: " + s);
                return s + " World";
            }
        }, executorService); // 使用自定义线程池执行

// 注意:在使用完自定义线程池后,应该关闭它以释放资源
executorService.shutdown();

// 可以链式调用其他方法来继续处理返回的值
CompletableFuture<String> finalFuture = future.thenApply(result -> "Final: " + result);
// 根据前面的handleAsync方法,可能输出 "Final: Hello World"
System.out.println(finalFuture.join());
一元依赖使用方式总结

一万两千字,一文讲懂如何使用​​CompletableFuture_线程池_04

这些方法的主要区别在于它们对异步性的处理(同步或异步)以及是否允许使用自定义的线程池。使用这些方法时,可以根据具体需求选择最合适的操作,以实现对异步计算结果的灵活处理。


二元依赖:AND组合关系
thenCombine/thenCombineAsync方法

CompletableFuturethenCombinethenCombineAsync 方法用于将两个异步操作的结果组合起来。当两个 CompletableFuture 都完成时,可以利用这两个结果执行某些操作。

  • 区别thenCombine 在任一输入的 CompletableFuture 完成时,使用默认的执行器来同步执行提供的组合函数。thenCombineAsync 允许异步执行组合函数,并且可以指定自定义的 ExecutorService
  • 返回值:两者都返回 CompletableFuture<V>,其中 V 是组合函数返回的值的类型。
  • 自定义线程池thenCombine 使用默认的 ForkJoinPool.commonPool(),而 thenCombineAsync 可以使用自定义的 ExecutorService
  • 执行方式thenCombine 是同步执行,而 thenCombineAsync 是异步执行。
thenCombine 的具体使用方法

thenCombine 方法在两个 CompletableFuture 都完成时,使用它们的结果同步执行一个组合函数。

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");

CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (s1, s2) -> s1 + ", " + s2);
System.out.println(combinedFuture.join()); // 输出 "Hello, World"

在这个例子中,我们异步地创建了两个 CompletableFuture 实例,当它们都完成时,使用它们的结果通过 thenCombine 方法组合成一个新的字符串。

thenCombineAsync 的具体使用方法

thenCombineAsync 方法在两个 CompletableFuture 都完成时,使用指定的 ExecutorService 异步执行组合函数。

ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建自定义的线程池

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");

CompletableFuture<String> combinedAsyncFuture = future1.thenCombineAsync(future2, (s1, s2) -> s1 + ", " + s2, executorService);
System.out.println(combinedAsyncFuture.join()); // 输出 "Hello, World"

// 注意:在使用完自定义线程池后,应该关闭它以释放资源
executorService.shutdown();

在这个例子中,我们使用 thenCombineAsync 方法并指定了一个自定义线程池来异步组合两个异步操作的结果。

thenAcceptBoth/thenAcceptBothAsync方法

CompletableFuturethenAcceptBoththenAcceptBothAsync 方法用于在两个异步操作完成后,使用两个 CompletableFuture 的结果执行一个动作。这两个方法都接受一个 BiAction 作为参数,该 BiAction 接受两个参数:第一个 CompletableFuture 的结果和第二个 CompletableFuture 的结果。

  • 区别thenAcceptBoth 在任一输入的 CompletableFuture 完成时,使用默认的执行器来同步执行提供的 BiActionthenAcceptBothAsync 允许异步执行 BiAction,并且可以指定自定义的 ExecutorService
  • 返回值:两者都返回 CompletableFuture<Void>,因为 BiAction 不返回任何结果。
  • 自定义线程池thenAcceptBoth 使用默认的 ForkJoinPool.commonPool(),而 thenAcceptBothAsync 可以使用自定义的 ExecutorService
  • 执行方式thenAcceptBoth 是同步执行,而 thenAcceptBothAsync 是异步执行。
thenAcceptBoth 的具体使用方法

thenAcceptBoth 方法在两个 CompletableFuture 都完成时,使用它们的结果同步执行一个 BiAction

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");

CompletableFuture<Void> bothFuture = future1.thenAcceptBoth(future2, (s1, s2) -> System.out.println(s1 + " " + s2));
// 输出 "Hello World",join() 用于等待操作完成
bothFuture.join();
thenAcceptBothAsync 的具体使用方法

thenAcceptBothAsync 方法在两个 CompletableFuture 都完成时,使用指定的 ExecutorService 异步执行 BiAction

// 创建自定义的线程池
ExecutorService executorService = Executors.newFixedThreadPool(3); 

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");

CompletableFuture<Void> bothAsyncFuture = future1.thenAcceptBothAsync(future2, (s1, s2) -> System.out.println(s1 + " " + s2), executorService);
// 输出 "Hello World",join() 用于等待操作完成
bothAsyncFuture.join(); 

// 注意:在使用完自定义线程池后,应该关闭它以释放资源
executorService.shutdown();
runAfterEither/runAfterEitherAsync方法

CompletableFuturerunAfterBothrunAfterBothAsync 方法用于在两个异步操作完成后执行一个 Runnable。这两个方法允许你指定一个动作,这个动作会在两个 CompletableFuture 都完成之后执行,与它们完成的顺序无关。

  • 区别runAfterBoth 在任一输入的 CompletableFuture 完成时,使用默认的执行器来同步执行提供的 RunnablerunAfterBothAsync 允许异步执行 Runnable,并且可以指定自定义的 ExecutorService
  • 返回值:两者都返回 CompletableFuture<Void>,因为 Runnable 不返回任何结果。
  • 自定义线程池runAfterBoth 使用默认的 ForkJoinPool.commonPool(),而 runAfterBothAsync 可以使用自定义的 ExecutorService
  • 执行方式runAfterBoth 是同步执行,而 runAfterBothAsync 是异步执行。
runAfterBoth 的具体使用方法

runAfterBoth 方法在两个 CompletableFuture 都完成时,使用它们的结果同步执行一个 Runnable

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");

CompletableFuture<Void> bothFuture = future1.runAfterBoth(future2, () -> System.out.println("Both futures are complete."));
// 输出 "Both futures are complete.",join() 用于等待操作完成
bothFuture.join();
runAfterBothAsync 的具体使用方法

runAfterBothAsync 方法在两个 CompletableFuture 都完成时,使用指定的 ExecutorService 异步执行 Runnable

ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建自定义的线程池

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");

CompletableFuture<Void> bothAsyncFuture = future1.runAfterBothAsync(future2, () -> System.out.println("Both futures are complete."), executorService);
// 输出 "Both futures are complete.",join() 用于等待操作完成
bothAsyncFuture.join();

// 注意:在使用完自定义线程池后,应该关闭它以释放资源
executorService.shutdown();
二元依赖:OR组合的关系
applyToEither/applyToEitherAsync方法

CompletableFutureapplyToEitherapplyToEitherAsync 方法用于在两个异步操作中,无论哪个操作先完成,都对第一个完成的操作的结果应用一个函数,并将函数的返回值封装进一个新的 CompletableFuture 中。

  • 区别applyToEither 会在任一提供的 CompletableFuture 完成时,使用默认的执行器来同步执行提供的函数。applyToEitherAsync 同样会在任一提供的 CompletableFuture 完成时执行函数,但它明确地异步执行函数,并且可以指定自定义的 ExecutorService
  • 返回值:两者都会返回一个新的 CompletableFuture,其类型是应用函数后的结果类型。
  • 自定义线程池applyToEither 使用默认的 ForkJoinPool.commonPool(),而 applyToEitherAsync 可以使用自定义的 ExecutorService
  • 执行方式applyToEither 是同步执行,applyToEitherAsync 是异步执行。
applyToEither 的具体使用方法

applyToEither 方法在两个 CompletableFuture 中任何一个完成时,会同步执行提供的函数。

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");

CompletableFuture<String> eitherFuture = future1.applyToEither(future2, s -> s.toUpperCase());
System.out.println(eitherFuture.join()); // 输出第一个完成的 CompletableFuture 的结果转换为大写

在这个例子中,我们异步地供应字符串 "Hello" 和 "World",无论哪个 CompletableFuture 先完成,都会同步执行 applyToEither 中的函数,将其结果转换为大写。

applyToEitherAsync 的具体使用方法

applyToEitherAsync 方法在两个 CompletableFuture 中任何一个完成时,会使用指定的 ExecutorService 异步执行提供的函数。

ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建自定义的线程池

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");

CompletableFuture<String> eitherAsyncFuture = future1.applyToEitherAsync(future2, s -> s.toUpperCase(), executorService);
System.out.println(eitherAsyncFuture.join()); // 输出第一个完成的 CompletableFuture 的结果转换为大写

// 注意:在使用完自定义线程池后,应该关闭它以释放资源
executorService.shutdown();

在这个例子中,我们创建了一个自定义的线程池,并在异步供应 "Hello" 和 "World" 之后,使用 applyToEitherAsync 来异步地执行一个任务,该任务将第一个完成的 CompletableFuture 的结果转换为大写。

acceptEither/acceptEitherAsync方法

CompletableFutureacceptEitheracceptEitherAsync 方法用于在两个异步操作中,无论哪个操作先完成,都执行一个 Consumer 对象,该 Consumer 接受操作的结果。

  • 区别acceptEither 会在任一提供的 CompletableFuture 完成时,使用默认的执行器来同步执行提供的 ConsumeracceptEitherAsync 也会在任一提供的 CompletableFuture 完成时执行 Consumer,但它明确地异步执行 Consumer,并且可以指定自定义的 ExecutorService
  • 返回值:两者都返回 CompletableFuture<Void>,因为 Consumer 接受一个参数并不返回任何结果。
  • 自定义线程池acceptEither 使用默认的 ForkJoinPool.commonPool(),而 acceptEitherAsync 可以使用自定义的 ExecutorService
  • 执行方式acceptEither 是同步执行,acceptEitherAsync 是异步执行。
acceptEither 的具体使用方法

acceptEither 方法在两个 CompletableFuture 中任何一个完成时,会同步执行提供的 Consumer

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");

CompletableFuture<Void> eitherFuture = future1.acceptEither(future2, s -> System.out.println(s));
eitherFuture.join(); // 输出第一个完成的 CompletableFuture 的结果

在这个例子中,我们异步地供应字符串 "Hello" 和 "World",无论哪个 CompletableFuture 先完成,都会同步执行 acceptEither 中的 Consumer,打印出结果。

acceptEitherAsync 的具体使用方法

acceptEitherAsync 方法在两个 CompletableFuture 中任何一个完成时,会使用指定的 ExecutorService 异步执行提供的 Consumer

ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建自定义的线程池

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");

CompletableFuture<Void> eitherAsyncFuture = future1.acceptEitherAsync(future2, s -> System.out.println(s), executorService);
eitherAsyncFuture.join(); // 输出第一个完成的 CompletableFuture 的结果

// 注意:在使用完自定义线程池后,应该关闭它以释放资源
executorService.shutdown();

在这个例子中,我们创建了一个自定义的线程池,并在异步供应 "Hello" 和 "World" 之后,使用 acceptEitherAsync 来异步地执行一个任务,该任务打印出第一个完成的 CompletableFuture 的结果。

runAfterEither/runAfterEitherAsync 方法

CompletableFuturerunAfterEitherrunAfterEitherAsync 方法用于在两个异步操作中的任何一个完成后执行一个 Runnable。这两个方法允许你指定一个动作,这个动作会在任何一个 CompletableFuture 完成之后执行,与它们完成的顺序无关。

  • 区别runAfterEither 会在任一提供的 CompletableFuture 完成时,使用默认的执行器来同步执行提供的 RunnablerunAfterEitherAsync 也会在任一提供的 CompletableFuture 完成时执行 Runnable,但它明确地异步执行 Runnable,并且可以指定自定义的 ExecutorService
  • 返回值:两者都返回 CompletableFuture<Void>,因为 Runnable 接受没有参数并不返回任何结果。
  • 自定义线程池runAfterEither 使用默认的 ForkJoinPool.commonPool(),而 runAfterEitherAsync 可以使用自定义的 ExecutorService
  • 执行方式runAfterEither 是同步执行,runAfterEitherAsync 是异步执行。
runAfterEither 的具体使用方法

runAfterEither 方法在两个 CompletableFuture 中任何一个完成时,会同步执行提供的 Runnable

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");

CompletableFuture<Void> eitherFuture = future1.runAfterEither(future2, () -> System.out.println("One of the futures is complete."));
eitherFuture.join(); // 确保 Runnable 执行完毕

在这个例子中,我们异步地供应字符串 "Hello" 和 "World",无论哪个 CompletableFuture 先完成,都会同步执行 runAfterEither 中的 Runnable,打印出一条消息。

runAfterEitherAsync 的具体使用方法

runAfterEitherAsync 方法在两个 CompletableFuture 中任何一个完成时,会使用指定的 ExecutorService 异步执行提供的 Runnable

ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建自定义的线程池

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");

CompletableFuture<Void> eitherAsyncFuture = future1.runAfterEitherAsync(future2, () -> System.out.println("One of the futures is complete."), executorService);
eitherAsyncFuture.join(); // 确保 Runnable 执行完毕

// 注意:在使用完自定义线程池后,应该关闭它以释放资源
executorService.shutdown();

在这个例子中,我们创建了一个自定义的线程池,并在异步供应 "Hello" 和 "World" 之后,使用 runAfterEitherAsync 来异步地执行一个任务,该任务打印出一条消息。

二元依赖使用方式总结

CompletableFuture 的 AND 组合关系方法(thenCombine, thenCombineAsync, thenAcceptBoth, thenAcceptBothAsync, runAfterBoth, runAfterBothAsync)在两个 CompletableFuture 都成功完成后执行某个操作,而 OR 组合关系方法(applyToEither, applyToEitherAsync, acceptEither, acceptEitherAsync, runAfterEither, runAfterEitherAsync)在两个 CompletableFuture 中任意一个完成时执行操作,其中 AND 组合关系方法关注结果的组合和消费,OR 组合关系方法关注对第一个完成的结果的快速反应。

一万两千字,一文讲懂如何使用​​CompletableFuture_异步操作_05

多元依赖:allOf/anyOf方法

CompletableFutureallOfanyOf 方法用于处理多个异步操作的完成情况。

  • allOf:等待多个 CompletableFuture 都完成。
  • anyOf:等待多个 CompletableFuture 中的任何一个完成。
  • 区别allOf 要求所有给定的 CompletableFuture 对象都完成,而 anyOf 只要求至少有一个完成。
  • 返回值:两者都返回一个新的 CompletableFuture<Void>,不包含具体业务返回值,表示所有或任何一个异步操作的完成。
  • 自定义线程池:这两个方法本身不涉及执行具体的异步操作,因此它们不使用线程池。它们仅在所有给定的 CompletableFuture 都完成时触发,而实际的异步操作可以使用自定义线程池。
  • 执行方式allOfanyOf 本身是同步执行的,因为它们仅在所有或任一 CompletableFuture 异步操作完成后才会触发。
allOf 的具体使用方法

allOf 方法在所有提供的 CompletableFuture 完成时,会完成返回的 CompletableFuture

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "!");

CompletableFuture<Void> allFuture = CompletableFuture.allOf(future1, future2, future3);

allFuture.thenRun(() -> System.out.println("Both futures are complete."));

如果想要获取每个 CompletableFuture 的结果,你可以使用 join() 方法或者 get() 方法在每个独立的 CompletableFuture 上。以下是如何获取这些返回值的示例:

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "!");

CompletableFuture<Void> allFuture = CompletableFuture.allOf(future1, future2, future3);

// 等待所有异步操作都完成
allFuture.join();

// 现在所有 future 都完成了,可以安全地获取它们的返回值
String result1 = future1.join(); // 获取 "Hello"
String result2 = future2.join(); // 获取 "World"
String result3 = future3.join(); // 获取 "!"

// 可以按需对结果进行处理,例如拼接字符串
String combinedResult = result1 + " " + result2 + " " + result3;
System.out.println(combinedResult); // 输出 "Hello World !"

在这个例子中,我们首先启动了三个异步操作,并通过 allOf 方法创建了一个等待所有这些操作都完成的 CompletableFuture。一旦 allFuture.join() 返回,我们就可以安全地调用 join() 方法在 future1future2future3 上,以获取它们的返回值。

请注意,join() 方法是阻塞的,它会等待对应的 CompletableFuture 完成。如果你在调用 join() 的时候不希望阻塞当前线程,可以使用 get() 方法,它提供了超时机制:

String result1 = future1.get(); // 这将阻塞直到 future1 完成
String result2 = future2.get(); // 这将阻塞直到 future2 完成
String result3 = future3.get(); // 这将阻塞直到 future3 完成

如果你确实需要异步地获取这些结果,并且对完成顺序没有特定要求,你可以使用 thenApplythenAccept 或者 whenComplete 等方法来处理每个 CompletableFuture 的结果。

anyOf 的具体使用方法

anyOf 方法在任一提供的 CompletableFuture 完成时,就会完成返回的 CompletableFuture

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "!");

CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(future1, future2, future3);

anyFuture.thenAccept(completedFuture ->
        System.out.println("One of the futures is complete."));

如果想要获取每个 CompletableFuture 的返回值,你需要分别对它们调用 join()get() 方法。以下是如何获取这些返回值的示例:

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "!");

CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(future1, future2, future3);

anyFuture.thenAccept(completedFuture ->
        System.out.println("One of the futures is complete."));

// 等待 anyFuture 完成,但这只告诉我们至少有一个 future 完成了,并不指明是哪一个
anyFuture.join();

// 检查每个 future 是否完成,并获取其结果
String result1 = future1.isDone() ? future1.join() : null;
String result2 = future2.isDone() ? future2.join() : null;
String result3 = future3.isDone() ? future3.join() : null;

// 打印结果
System.out.println("Result from future1: " + result1);
System.out.println("Result from future2: " + result2);
System.out.println("Result from future3: " + result3);

在这个例子中,我们首先启动了三个异步操作,并通过 anyOf 方法创建了一个 CompletableFuture,该 CompletableFuture 将在任一异步操作完成时完成。我们使用 thenAccept 方法来处理至少一个异步操作完成的情况。

然后,我们使用 join() 方法等待 anyFuture 完成,随后检查每个 CompletableFuture 是否完成,并通过调用 join() 来获取它们的返回值。这里我们假设一旦 anyFuture 完成,其他 CompletableFuture 也可能会完成。如果某个 CompletableFuture 还没有完成,join() 方法会阻塞当前线程直到它完成。

请注意,这种方法可能不是效率最高的,特别是如果 CompletableFuture 完成的时间差异较大时。如果你确实需要知道哪个 CompletableFuture 首先完成,你可能需要使用其他策略,例如为每个 CompletableFuture 添加一个单独的完成处理程序,或者使用原子引用来存储第一个完成的结果。

注意事项
  • allOfanyOf 本身不执行异步操作,它们只是等待其他 CompletableFuture 的完成。因此,使用这些方法时,你的异步操作应该已经通过其他方式(如 supplyAsync)在自定义线程池中执行。
  • 这些方法通常用于在异步操作完成后执行某些同步逻辑,如结果汇总、后续步骤触发等。
  • 使用 allOf 时,如果任何一个 CompletableFuture 完成异常,返回的 CompletableFuture 也会异常结束。而 anyOf 会忽略其他 CompletableFuture 的异常,只关注任一完成。
异常处理: exceptionally

CompletableFutureexceptionally 方法用于处理异步操作中的异常。当 CompletableFuture 完成时,如果它完成了一个异常(即操作失败),那么 exceptionally 方法中提供的函数将被调用。这个方法允许你提供一个函数,该函数能够返回一个非空的值,这个值将用作 CompletableFuture 的结果。

  • 返回值exceptionally 方法返回 CompletableFuture<T>,其中 T 是函数返回的类型。
  • 自定义线程池exceptionally 方法本身不涉及线程池的使用,它总是在 CompletableFuture 所在线程的默认执行器上同步执行。
  • 执行方式exceptionally 是同步执行的。
具体使用方法

exceptionally 方法在 CompletableFuture 由于异常而完成时,执行一个函数来处理这个异常,并返回一个替代的结果。

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    if (Math.random() > 0.5) {
        throw new RuntimeException("Operation failed!");
    }
    return 42;
}).exceptionally(throwable -> {
    // 异常处理逻辑
    System.out.println("Handling exception: " + throwable.getMessage());
    // 返回一个替代的结果
    return -1;
});

System.out.println("Future result: " + future.join()); // 如果有异常,输出将是 -1

在这个例子中,我们异步地执行一个可能抛出异常的操作。如果操作失败并抛出异常,exceptionally 方法中的函数将被调用,打印异常信息,并返回一个替代的结果 -1

注意事项
  • exceptionally 方法只能捕获并处理异步操作中的异常,它不能捕获在调用 exceptionally 方法之后发生的异常。
  • exceptionally 方法应该谨慎使用,因为它可能会隐藏错误并导致程序状态不一致。通常,更推荐使用 handle 方法来同时处理正常结果和异常。
  • 由于 exceptionally 方法在 CompletableFuture 完成的线程上执行,所以它不会创建新的线程,也不涉及自定义线程池的使用。
获取任务状态:isCancelled/isCompletedExceptionally

CompletableFuture 类中的 isCancelledisCompletedExceptionally 是两个用于检查异步操作状态的方法。下面是它们的使用方式和区别:

isCancelled()的具体使用方法

isCancelled() 方法用于检查 CompletableFuture 是否已经被取消。如果 CompletableFuture 被取消,且在取消时它还没有完成任何其他终端状态(即没有成功执行也没有异常),则返回 true

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    // 执行一些异步操作
    System.out.println("Async operation completed");
});

// 尝试取消 future,参数 true 表示中断正在执行的线程
future.cancel(true);

// 检查是否取消
boolean isCancelled = future.isCancelled(); 
System.out.println("Is future cancelled? " + isCancelled);
isCompletedExceptionally()的具体使用方法

isCompletedExceptionally() 方法用于检查 CompletableFuture 是否由于异常而完成。如果 CompletableFuture 由于异常(包括取消异常)而完成,返回 true

直接调用 future.isCompletedExceptionally() 不会立即返回 true,因为 CompletableFuture.runAsync() 中的代码是异步执行的,所以异常抛出的操作还没有完成。要确保能够检查到异常完成状态,你需要等待 CompletableFuture 真正完成。这可以通过调用 join()get() 方法实现,但这两种方法都是阻塞的。

可以通过几种方式实现:

  1. 在提供的异步函数中抛出异常。
  2. 使用 completeExceptionally 方法手动完成 CompletableFuture 并传入一个异常。
示例 1: 异步函数抛出异常
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    throw new RuntimeException("Async operation failed");
});

try {
    // 等待 future 完成
    future.join();
} catch (CompletionException e) {
    // 如果 future 由于异常而完成,CompletionException 会被抛出
    System.out.println("Async operation failed: " + e.getCause().getMessage());
}

boolean completedExceptionally = future.isCompletedExceptionally();
// 如果抛出了异常,这里将输出 true
System.out.println("Is future completed exceptionally? " + completedExceptionally);

在这个示例中,异步函数抛出了一个 RuntimeException,这将导致 CompletableFuture 异常完成,因此 isCompletedExceptionally() 将返回 true

示例 2: 使用 completeExceptionally 手动完成
CompletableFuture<Void> future = new CompletableFuture<>();

// 模拟异步操作中出现的异常情况
future.completeExceptionally(new RuntimeException("Manual exception completion"));

boolean completedExceptionally = future.isCompletedExceptionally();
// 将输出 true
System.out.println("Is future completed exceptionally? " + completedExceptionally);

在这个示例中,我们使用 completeExceptionally 方法手动将 CompletableFuture 设置为异常完成状态,并传入一个 RuntimeException 对象。这样,isCompletedExceptionally() 也会返回 true

请注意,一旦 CompletableFuture 通过 completeExceptionally 被设置为异常完成,它就不能被取消或正常完成了。此外,使用 completeExceptionally 是在 CompletableFuture 尚未完成时的一种操作,如果它已经完成了,那么调用 completeExceptionally 将没有效果。

使用场景
  • isCancelled() 通常用于确定是否需要采取一些操作,比如资源清理,当知道 CompletableFuture 被取消了之后。
  • isCompletedExceptionally() 可以在你想要检查 CompletableFuture 是否由于遇到问题而未能正常完成时使用,这包括了操作失败的异常以及完成时的取消异常。
注意事项
  • 一旦 CompletableFuture 完成,无论是正常完成、异常完成还是被取消,isCancelledisCompletedExceptionally 的状态就不会改变。
  • 在调用 isCancelled()isCompletedExceptionally() 之前,你应该确保 CompletableFuture 已经达到了一个终端状态,否则这两个方法可能返回不准确的结果。

这两个方法是非阻塞的,通常用于异步操作的协调和状态检查。

状态监控:getNumberOfDependents

CompletableFuturegetNumberOfDependents 方法返回一个整数,表示有多少异步操作或者依赖是注册在当前 CompletableFuture 实例上的。这个方法可以用来了解和调试 CompletableFuture 对象上的链式调用情况。

这个方法的使用非常简单,它不涉及任何参数,直接返回一个整数值:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(5000);
    }
    catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "Hello";
});

// 添加一些依赖操作
future.thenApply(s -> s.toUpperCase());
future.thenAccept(s -> System.out.println(s));
future.exceptionally(e -> {
    System.out.println("An exception occurred: " + e.getMessage());
    return null;
});

// 获取依赖的数量
int numberOfDependents = future.getNumberOfDependents();
System.out.println("Number of dependents: " + numberOfDependents);

如果在调用 getNumberOfDependents 方法时,所有的依赖操作都还没有完成,那么理论上 numberOfDependents 应该返回 3,因为您添加了三个依赖。

然而,有几个原因可能导致 getNumberOfDependents 返回 0

  1. 异步操作完成太快:如果 supplyAsync 非常快速地完成了,那么在您调用 getNumberOfDependents 之前,所有的依赖操作可能都已经完成了。
  2. 调用顺序问题:如果在调用 getNumberOfDependents 之前,您做了某些事情(如调用了 joinget 或其他可能导致 CompletableFuture 完成的操作),那么这可能触发了依赖的执行和完成。
  3. JDK 实现细节getNumberOfDependents 方法的实现可能依赖于内部的计数器,这些计数器可能在依赖完成时递减。如果依赖完成得非常快,或者在调用 getNumberOfDependents 之前有其他操作影响了这些计数器,那么它们可能已经被清零了。

为了确保 getNumberOfDependents 返回正确的数量,您可以尝试以下方法:

  • 确保在调用 getNumberOfDependents 之前,没有执行任何可能导致 CompletableFuture 或其依赖完成的操作。
  • 使用 CompletableFuturewhenComplete 方法来在异步操作完成后立即调用 getNumberOfDependents

请注意,getNumberOfDependents 主要用于调试目的,不应该依赖它来控制程序的主要逻辑。在实际应用中,依赖的数量通常不是非常重要,更重要的是正确地管理和协调异步操作。

强制处理:obtrudeValue/obtrudeException方法
obtrudeValue(T value)的具体使用方法

obtrudeValue 方法用于将 CompletableFuture 的结果强制设置为提供的值。如果 CompletableFuture 已经以任何方式完成(无论是正常完成、异常完成还是被取消),那么调用 obtrudeValue 将不会有任何效果。

CompletableFuture<String> future = new CompletableFuture<>();

future.obtrudeValue("Hello World"); // 强制完成 future 并赋予值
future.thenAccept(System.out::println); // 这将打印 "Hello World"
obtrudeException(Throwable throwable)的具体使用方法

obtrudeException 方法用于将 CompletableFuture 的结果强制设置为由提供的异常引起的异常完成。同样,如果 CompletableFuture 已经完成,调用 obtrudeException 将不会有任何效果。

CompletableFuture<String> future = new CompletableFuture<>();

future.obtrudeException(new RuntimeException("Something went wrong")); // 强制完成 future 并赋予异常
future.exceptionally(e -> {
    System.out.println(e.getMessage()); // 这将打印异常信息 "Something went wrong"
    return null;
});
使用场景
  • obtrudeValueobtrudeException 可以在需要覆盖 CompletableFuture 的完成状态时使用,例如,在某些错误恢复场景中,你可能需要将 CompletableFuture 强制设置为成功或失败状态。
  • 这些方法也可以用来在程序的其他部分手动触发 CompletableFuture 的完成,即使异步操作尚未开始或已经取消。
注意事项
  • obtrudeValueobtrudeException 应该谨慎使用,因为它们会覆盖 CompletableFuture 的自然完成状态,可能导致程序逻辑的混乱。
  • 一旦 CompletableFuture 完成,无论是通过正常完成、异常完成、取消还是通过这些方法之一,它的状态就不能被改变了。
  • 这些方法适用于需要手动干预异步操作结果的场景,例如,在某些错误恢复策略中,或者当你需要根据外部事件强制完成 CompletableFuture 时。

在实际编程中,通常推荐使用 CompletableFuture 的标准完成方法,如 completecompleteExceptionally,因为它们更符合 CompletableFuture 的预期使用模式。obtrudeValueobtrudeException 应该用于特定的、非标准的场景。