CompletableFuture#allOf 源码解析&最佳实践

一、源码


    /**
     * Returns a new CompletableFuture that is completed when all of
     * the given CompletableFutures complete.  If any of the given
     * CompletableFutures complete exceptionally, then the returned
     * CompletableFuture also does so, with a CompletionException
     * holding this exception as its cause.  Otherwise, the results,
     * if any, of the given CompletableFutures are not reflected in
     * the returned CompletableFuture, but may be obtained by
     * inspecting them individually. If no CompletableFutures are
     * provided, returns a CompletableFuture completed with the value
     * {@code null}.
     *
     * 

Among the applications of this method is to await completion * of a set of independent CompletableFutures before continuing a * program, as in: {@code CompletableFuture.allOf(c1, c2, * c3).join();}. * * @param cfs the CompletableFutures * @return a new CompletableFuture that is completed when all of the * given CompletableFutures complete * @throws NullPointerException if the array or any of its elements are * {@code null} */ public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) { return andTree(cfs, 0, cfs.length - 1); }

二、源码解析

这段代码定义了 CompletableFuture 类中的 allOf 静态方法。该方法的主要作用是:

    1. 接收一个或多个 CompletableFuture 对象作为参数
    1. 返回一个新的 CompletableFuture 对象,该对象在所有输入的 CompletableFuture 都完成时才会完成

详细解释:

1. 方法签名:

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
  • 这是一个静态方法,可以直接通过类名调用
  • 参数 cfs 是一个可变参数,可以接收任意数量的 CompletableFuture 对象
  • 返回类型是 CompletableFuture,表示不关心具体的返回值,只关心完成状态

2. 方法实现:

return andTree(cfs, 0, cfs.length - 1);
  • 调用了一个名为 andTree 的私有方法,传入了 cfs 数组及其起始和结束索引
  • andTree 方法可能使用了树形结构来组合多个 CompletableFuture,以实现高效的并行处理

3. 异常处理:

  • 如果任何一个输入的 CompletableFuture 异常完成,返回的 CompletableFuture 也会异常完成
  • 异常会被包装在 CompletionException

4. 特殊情况:

  • 如果没有提供任何 CompletableFuture,返回一个已完成的 CompletableFuture,其值为 null

      1. 当调用 CompletableFuture.allOf() 方法时,如果没有传入任何参数,即:
    CompletableFuture<Void> result = CompletableFuture.allOf();
    
      1. 方法会返回一个特殊的 CompletableFuture 对象:
      • 这个对象已经处于完成状态(completed)
      • 它的结果值为 null
      1. 这是一个边界情况的处理:
      • 当没有任何任务需要等待时,逻辑上认为"所有任务都已完成"
      • 返回一个已完成的 CompletableFuture 保持了方法的一致性
      1. 实际使用中的含义:
      • 如果你对这个返回的 CompletableFuture 调用 get()join(),它会立即返回,不会阻塞
      • 返回的值将是 null(因为 Void 类型在 Java 中只能是 null
      1. 这种设计使得方法调用在没有参数的情况下也能正常工作,增加了 API 的健壮性和易用性

这种处理方式符合"空对象模式"的设计理念,避免了在没有任务时返回 null 或抛出异常,使得调用代码可以统一处理各种情况。

这个方法为并发编程提供了一种简洁有效的方式来协调多个异步操作的完成。

三、最佳实践 & 示例

CompletableFuture.allOf 的最佳实践及示例如下:

1. 并行执行多个独立任务

CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
    // 模拟耗时操作
    sleep(1000);
    return "Result of Task 1";
});

CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
    sleep(2000);
    return "Result of Task 2";
});

CompletableFuture<String> task3 = CompletableFuture.supplyAsync(() -> {
    sleep(3000);
    return "Result of Task 3";
});

CompletableFuture.allOf(task1, task2, task3).join();

System.out.println(task1.get());
System.out.println(task2.get());
System.out.println(task3.get());

2. 组合多个任务的结果

List<CompletableFuture<String>> futures = Arrays.asList(task1, task2, task3);

CompletableFuture<List<String>> allResults = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
    .thenApply(v -> futures.stream()
        .map(CompletableFuture::join)
        .collect(Collectors.toList()));

List<String> results = allResults.get();
results.forEach(System.out::println);
  • 这段代码展示了如何组合多个 CompletableFuture 的结果。让我们逐步解析:

    1. 创建 CompletableFuture 列表:
    List<CompletableFuture<String>> futures = Arrays.asList(task1, task2, task3);
    
    • 将多个 CompletableFuture 任务放入一个列表中
    1. 使用 CompletableFuture.allOf:
    CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
    
    • futures.toArray(new CompletableFuture[0]) 将 List 转换为数组
    • CompletableFuture.allOf 创建一个新的 CompletableFuture,它会在所有输入的 CompletableFuture 完成时完成
    1. 使用 thenApply 处理结果:
    .thenApply(v -> futures.stream()
        .map(CompletableFuture::join)
        .collect(Collectors.toList()))
    
    • thenApply 在 allOf 完成后执行
    • v 是 void 类型,因为 allOf 返回 CompletableFuture
    • 创建一个 stream 处理 futures 列表
    • 对每个 future 调用 join() 方法获取结果
    • 将结果收集到一个新的 List 中
    1. 整体结果:
    CompletableFuture<List<String>> allResults = ...
    
    • allResults 是一个 CompletableFuture,它的结果是所有任务结果的列表
    1. 获取最终结果:
    List<String> results = allResults.get();
    
    • 调用 get() 等待 allResults 完成并获取结果列表
    1. 输出结果:
    results.forEach(System.out::println);
    
    • 遍历并打印每个任务的结果
  • 关键点:

    1. 这种方法允许同时等待多个任务完成并收集它们的结果
    1. 使用 stream 和 lambda 表达式简化了代码
    1. join() 方法用于获取每个 CompletableFuture 的结果,它类似于 get(),但不会抛出受检异常
    1. 这种方法在处理大量并行任务时特别有用
    1. 注意,如果任何一个任务抛出异常,get() 调用会抛出 ExecutionException

这种模式非常适合需要并行执行多个任务并收集所有结果的场景,既提高了效率,又保持了代码的简洁性。

3. 处理异常

CompletableFuture<String> failingTask = CompletableFuture.supplyAsync(() -> {
    throw new RuntimeException("Task failed");
});

CompletableFuture.allOf(task1, task2, failingTask)
    .exceptionally(ex -> {
        System.out.println("An error occurred: " + ex.getMessage());
        return null;
    })
    .join();

这段代码展示了如何处理 CompletableFuture 中的异常。

  1. 创建一个会失败的任务:

    CompletableFuture<String> failingTask = CompletableFuture.supplyAsync(() -> {
        throw new RuntimeException("Task failed");
    });
    
    • 使用 CompletableFuture.supplyAsync 创建一个异步任务
    • 这个任务会抛出一个 RuntimeException,模拟任务失败的情况
  2. 使用 CompletableFuture.allOf:

    CompletableFuture.allOf(task1, task2, failingTask)
    
    • 组合多个 CompletableFuture,包括可能失败的任务
    • 如果任何一个任务失败,allOf 返回的 CompletableFuture 也会失败
  3. 异常处理:

    .exceptionally(ex -> {
        System.out.println("An error occurred: " + ex.getMessage());
        return null;
    })
    
    • exceptionally 方法用于处理 CompletableFuture 链中的异常
    • 它接收一个 Function,其中 T 是 CompletableFuture 的结果类型
    • 在这个例子中,我们打印错误消息并返回 null
    • 返回 null 是因为 allOf 返回 CompletableFuture,所以异常处理也需要返回 Void(null)
  4. 等待完成:

    .join();
    
    • join() 方法等待 CompletableFuture 完成
    • get() 类似,但不会抛出受检异常

关键点:

  1. 这种方式允许在一组任务中优雅地处理异常,而不会中断整个流程
  2. exceptionally 方法只在发生异常时被调用
  3. 如果没有使用 exceptionally,异常会在 join() 调用时抛出
  4. 这种模式适用于需要继续执行,即使某些任务失败的场景
  5. 在实际应用中,你可能需要根据异常类型采取不同的处理策略

注意事项:

  • 这个例子中,即使有任务失败,其他任务仍会继续执行
  • 如果需要在第一个任务失败时就停止所有任务,需要使用不同的策略(如 CompletableFuture.anyOf 配合自定义逻辑)
  • 在生产环境中,可能需要更详细的日志记录和错误处理

这种异常处理方式使得 CompletableFuture 能够在复杂的异步场景中提供更强大和灵活的错误管理能力。

4. 设置超时

CompletableFuture<Void> allOf = CompletableFuture.allOf(task1, task2, task3);

try {
    allOf.get(5, TimeUnit.SECONDS);
} catch (TimeoutException e) {
    System.out.println("Operation timed out");
}

这段代码展示了如何为 CompletableFuture.allOf 设置超时。

  1. 创建组合任务:

    CompletableFuture<Void> allOf = CompletableFuture.allOf(task1, task2, task3);
    
    • 使用 CompletableFuture.allOf 组合多个任务
    • allOf 返回一个 CompletableFuture,它会在所有输入的 CompletableFuture 完成时完成
  2. 设置超时并等待结果:

    try {
        allOf.get(5, TimeUnit.SECONDS);
    } catch (TimeoutException e) {
        System.out.println("Operation timed out");
    }
    
    • 使用 get 方法的重载版本,它接受超时时间和时间单位
    • 尝试在 5 秒内获取 allOf 的结果
    • 如果 5 秒内所有任务没有完成,将抛出 TimeoutException
  3. 异常处理:

    • 代码捕获 TimeoutException 并打印超时消息
    • 注意,实际上 get 方法可能抛出其他异常:
      • InterruptedException:如果等待过程中线程被中断
      • ExecutionException:如果任何一个任务执行过程中抛出异常

关键点:

  1. 超时设置非常重要,可以防止程序无限期等待
  2. 超时发生后,底层的任务可能仍在继续执行
  3. 这种方法适用于有严格时间要求的场景,如服务响应时间限制

最佳实践:

  1. 根据业务需求合理设置超时时间
  2. 考虑在超时后如何处理仍在运行的任务(可能需要取消)
  3. 在实际应用中,可能需要更复杂的错误处理逻辑

示例扩展:

try {
    allOf.get(5, TimeUnit.SECONDS);
} catch (TimeoutException e) {
    System.out.println("Operation timed out");
    // 取消所有任务
    task1.cancel(true);
    task2.cancel(true);
    task3.cancel(true);
} catch (InterruptedException e) {
    System.out.println("Operation was interrupted");
    Thread.currentThread().interrupt(); // 重新设置中断状态
} catch (ExecutionException e) {
    System.out.println("An error occurred during execution: " + e.getCause().getMessage());
}

这个扩展版本处理了所有可能的异常,并在超时时尝试取消任务。

使用超时机制可以增加程序的健壮性,特别是在处理网络请求或其他可能长时间运行的操作时。它允许你的程序在预定的时间内做出反应,而不是无限期地等待。

5. 与 CompletableFuture.anyOf 结合使用

CompletableFuture<Object> anyOf = CompletableFuture.anyOf(task1, task2, task3);
CompletableFuture<Void> allOf = CompletableFuture.allOf(task1, task2, task3);

CompletableFuture.allOf(anyOf, allOf)
    .thenRun(() -> {
        System.out.println("First completed: " + anyOf.join());
        System.out.println("All completed");
    })
    .join();

这段代码展示了如何结合使用 CompletableFuture.anyOfCompletableFuture.allOf

  1. 创建 anyOf 任务:

    CompletableFuture<Object> anyOf = CompletableFuture.anyOf(task1, task2, task3);
    
    • CompletableFuture.anyOf 返回一个新的 CompletableFuture,它会在任何一个输入的 CompletableFuture 完成时完成
    • 返回类型是 Object,因为它可能返回任何一个任务的结果
  2. 创建 allOf 任务:

    CompletableFuture<Void> allOf = CompletableFuture.allOf(task1, task2, task3);
    
    • CompletableFuture.allOf 返回一个新的 CompletableFuture,它会在所有输入的 CompletableFuture 完成时完成
    • 返回类型是 Void,因为它不返回具体的结果值
  3. 组合 anyOf 和 allOf:

    CompletableFuture.allOf(anyOf, allOf)
    
    • 这里再次使用 allOf,但这次是等待 anyOfallOf 都完成
  4. 添加完成后的操作:

    .thenRun(() -> {
        System.out.println("First completed: " + anyOf.join());
        System.out.println("All completed");
    })
    
    • thenRun 添加一个在所有任务完成后执行的操作
    • 打印第一个完成的任务的结果(通过 anyOf.join()
    • 打印所有任务完成的消息
  5. 等待完成:

    .join();
    
    • join() 等待整个组合的 CompletableFuture 完成

关键点:

  1. 这种组合允许同时监控"任何一个任务完成"和"所有任务完成"的情况
  2. anyOf 在任何一个任务完成时就会完成,而 allOf 要等所有任务都完成
  3. 最外层的 allOf 确保在打印结果时,所有操作都已完成
  4. join() 用于等待最终结果,它不会抛出受检异常

使用场景:

  • 当你需要知道哪个任务最先完成,同时又要等待所有任务完成时
  • 在复杂的并行处理中,需要不同级别的完成通知

注意事项:

  • anyOf.join() 可能会抛出未检查的异常,如果任何任务异常完成
  • 在实际应用中,可能需要更复杂的错误处理逻辑
  • 这种模式在处理多个独立但相关的异步操作时特别有用

示例扩展:

CompletableFuture.allOf(anyOf, allOf)
    .thenRun(() -> {
        try {
            System.out.println("First completed: " + anyOf.join());
            System.out.println("All completed");
        } catch (CompletionException e) {
            System.out.println("An error occurred: " + e.getCause().getMessage());
        }
    })
    .exceptionally(ex -> {
        System.out.println("Unexpected error: " + ex.getMessage());
        return null;
    })
    .join();

这个扩展版本增加了异常处理,使代码更加健壮。

这种组合使用 anyOfallOf 的方式提供了强大的灵活性,允许你在复杂的异步场景中精确控制和监控任务的完成状态。

最佳实践总结:

  1. 使用 CompletableFuture.allOf 等待多个并行任务完成
  2. 结合 thenApplythenAccept 处理所有任务的结果
  3. 使用 exceptionallyhandle 处理可能发生的异常
  4. 考虑设置超时,避免无限期等待
  5. 根据需要,将 allOf 与其他 CompletableFuture 方法结合使用
  6. 注意内存使用,特别是处理大量任务时
  7. 考虑使用自定义的线程池来控制并发度

这些示例展示了 CompletableFuture.allOf 的多种用法,涵盖了常见的并发编程场景。根据具体需求,你可以选择合适的模式并进行适当的调整。

你可能感兴趣的:(并发,开发语言,java)