CompletableFuture 详解

在 Java 并发编程领域,CompletableFuture 是一个强大的工具,它不仅实现了 Future 接口,还提供了丰富的组合、链接和处理异步计算结果的功能。本文将深入探讨 CompletableFuture 的核心概念、API 以及实际应用场景。

一、CompletableFuture 概述

1.1 什么是 CompletableFuture

CompletableFuture 是 Java 8 引入的一个类,位于 java.util.concurrent 包下,它实现了 Future 和 CompletionStage 接口。Future 表示一个异步计算的结果,但它提供的功能有限,而 CompletableFuture 则扩展了这些功能,允许我们以声明式的方式处理异步计算结果。

1.2 为什么需要 CompletableFuture

在 Java 8 之前,处理异步计算主要依靠 Future 接口,但 Future 有以下局限性:

  • 无法手动完成计算(除非通过 FutureTask 的 set 方法)
  • 缺乏组合多个 Future 的能力
  • 没有提供异常处理机制
  • 不能注册回调函数

CompletableFuture 解决了这些问题,它提供了约 50 种不同的方法来组合、链接和处理 CompletableFuture 实例的结果。

二、CompletableFuture 基础用法

2.1 创建 CompletableFuture

2.1.1 创建一个已完成的 CompletableFuture

CompletableFuture<String> completedFuture = CompletableFuture.completedFuture("Hello, World!");
String result = completedFuture.get(); // 立即返回 "Hello, World!"

2.1.2 创建一个尚未完成的 CompletableFuture

CompletableFuture<String> future = new CompletableFuture<>();
// 稍后在其他线程中完成这个 future
new Thread(() -> {
    try {
        Thread.sleep(1000);
        future.complete("Hello after 1 second");
    } catch (InterruptedException e) {
        future.completeExceptionally(e);
    }
}).start();

2.1.3 使用 supplyAsync 创建一个异步执行的 CompletableFuture

// 使用默认线程池(ForkJoinPool.commonPool())
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    return "Result from supplyAsync";
});

// 使用自定义线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    return "Result from supplyAsync with custom executor";
}, executor);

2.1.4 使用 runAsync 创建一个不返回结果的 CompletableFuture

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    try {
        Thread.sleep(1000);
        System.out.println("Task completed");
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
});

2.2 获取结果

2.2.1 使用 get() 方法(可能阻塞)

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");
String result = future.get(); // 阻塞直到任务完成

2.2.2 使用 join() 方法(不抛出检查型异常)

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    if (Math.random() > 0.5) {
        throw new RuntimeException("Random failure");
    }
    return "Hello";
});
String result = future.join(); // 阻塞直到任务完成,遇到异常会抛出CompletionException

2.2.3 使用 getNow() 获取当前结果或默认值

CompletableFuture<String> future = new CompletableFuture<>();
String result = future.getNow("Default Value"); // 如果future未完成,返回默认值

2.2.4 使用 completeOnTimeout 设置超时后完成的值

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    return "Result after 2 seconds";
});
// 如果在1秒内未完成,则返回默认值
CompletableFuture<String> result = future.completeOnTimeout("Default on timeout", 1, TimeUnit.SECONDS);

2.2.5 使用 orTimeout 设置超时异常

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    return "Result after 2 seconds";
});
// 如果在1秒内未完成,则抛出TimeoutException
CompletableFuture<String> result = future.orTimeout(1, TimeUnit.SECONDS);

三、CompletableFuture 组合操作

3.1 转换结果(thenApply、thenApplyAsync)

thenApply 方法用于对 CompletableFuture 的结果进行转换,类似于 Stream 中的 map 操作。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
    .thenApply(s -> s + " World")
    .thenApply(s -> s.toUpperCase());

// 输出: HELLO WORLD
System.out.println(future.join());

thenApplyAsync 与 thenApply 类似,但它会在另一个线程中执行转换操作。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    System.out.println("Supply in thread: " + Thread.currentThread().getName());
    return "Hello";
}).thenApplyAsync(s -> {
    System.out.println("Apply in thread: " + Thread.currentThread().getName());
    return s + " World";
});

3.2 消费结果(thenAccept、thenAcceptAsync)

thenAccept 方法用于消费 CompletableFuture 的结果,不返回任何值。

CompletableFuture.supplyAsync(() -> "Hello")
    .thenAccept(s -> System.out.println("Received: " + s));

3.3 完成后执行(thenRun、thenRunAsync)

thenRun 方法在 CompletableFuture 完成后执行一个 Runnable,但不关心其结果。

CompletableFuture.supplyAsync(() -> {
    System.out.println("Task is running");
    return "Result";
}).thenRun(() -> System.out.println("Task is completed"));

3.4 组合两个 CompletableFuture(thenCompose)

thenCompose 方法用于组合两个依赖的异步操作,类似于 Stream 中的 flatMap 操作。

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

3.5 组合两个独立的 CompletableFuture(thenCombine)

thenCombine 方法用于组合两个独立的异步操作,并对它们的结果进行处理。

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

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

3.6 等待多个 CompletableFuture 完成(allOf、anyOf)

allOf 方法等待所有给定的 CompletableFuture 完成。

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Result 1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Result 2");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "Result 3");

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

// 等待所有任务完成
allOfFuture.join();

// 获取各个结果
String result1 = future1.join();
String result2 = future2.join();
String result3 = future3.join();

anyOf 方法等待任意一个给定的 CompletableFuture 完成。

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
    try { Thread.sleep(300); } catch (InterruptedException e) {}
    return "Result 1";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
    try { Thread.sleep(200); } catch (InterruptedException e) {}
    return "Result 2";
});
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
    try { Thread.sleep(100); } catch (InterruptedException e) {}
    return "Result 3";
});

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

// 输出: Result 3(因为它最快完成)
System.out.println(anyOfFuture.join());

四、CompletableFuture 异常处理

4.1 异常处理(exceptionally)

exceptionally 方法用于处理 CompletableFuture 中的异常情况。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    if (true) {
        throw new RuntimeException("Something went wrong");
    }
    return "Normal result";
}).exceptionally(ex -> {
    System.out.println("Caught exception: " + ex.getMessage());
    return "Default value";
});

// 输出: Default value
System.out.println(future.join());

4.2 处理结果或异常(handle)

handle 方法同时处理正常结果和异常情况。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    if (Math.random() > 0.5) {
        throw new RuntimeException("Random failure");
    }
    return "Success";
}).handle((result, ex) -> {
    if (ex != null) {
        System.out.println("Handled exception: " + ex.getMessage());
        return "Recovered from failure";
    }
    return result;
});

4.3 执行完成后的处理(whenComplete)

whenComplete 方法在 CompletableFuture 完成后执行,无论是否发生异常。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    if (Math.random() > 0.5) {
        throw new RuntimeException("Random failure");
    }
    return "Success";
}).whenComplete((result, ex) -> {
    if (ex != null) {
        System.out.println("Operation failed: " + ex.getMessage());
    } else {
        System.out.println("Operation succeeded with result: " + result);
    }
});

五、CompletableFuture 高级用法

5.1 自定义线程池

CompletableFuture 默认使用 ForkJoinPool.commonPool(),但在实际生产环境中,建议使用自定义线程池以避免耗尽公共线程池资源。

ExecutorService customExecutor = Executors.newFixedThreadPool(10);

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 异步任务
    return "Result";
}, customExecutor);

// 使用完后关闭线程池
customExecutor.shutdown();

5.2 并行处理列表

CompletableFuture 非常适合并行处理列表中的元素。

List<String> items = Arrays.asList("item1", "item2", "item3", "item4", "item5");
ExecutorService executor = Executors.newFixedThreadPool(5);

List<CompletableFuture<String>> futures = items.stream()
    .map(item -> CompletableFuture.supplyAsync(() -> {
        // 处理每个元素
        try { Thread.sleep(100); } catch (InterruptedException e) {}
        return "Processed " + item;
    }, executor))
    .collect(Collectors.toList());

// 等待所有任务完成并收集结果
List<String> results = futures.stream()
    .map(CompletableFuture::join)
    .collect(Collectors.toList());

executor.shutdown();

5.3 异步方法链的构建

CompletableFuture 的一大优势是可以构建复杂的异步方法链。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    System.out.println("Step 1: Prepare data");
    return "Data";
}).thenApplyAsync(data -> {
    System.out.println("Step 2: Process data");
    return "Processed " + data;
}).thenCombineAsync(CompletableFuture.supplyAsync(() -> {
    System.out.println("Step 3: Prepare additional data");
    return "Additional data";
}), (result1, result2) -> {
    System.out.println("Step 4: Combine results");
    return result1 + " and " + result2;
}).thenApplyAsync(finalResult -> {
    System.out.println("Step 5: Final processing");
    return finalResult.toUpperCase();
}).exceptionally(ex -> {
    System.out.println("Error occurred: " + ex.getMessage());
    return "Default result";
});

System.out.println("Final result: " + future.join());

六、CompletableFuture 性能与最佳实践

6.1 性能考虑

  1. 使用自定义线程池:避免使用默认的 ForkJoinPool.commonPool(),特别是在高并发环境下。

  2. 合理设置线程池大小:根据任务性质和系统资源设置合适的线程池大小。

  3. 避免阻塞操作:在 CompletableFuture 的回调中避免执行阻塞操作,以免占用线程池资源。

6.2 最佳实践

  1. 总是处理异常:使用 exceptionally、handle 或 whenComplete 处理异常,避免异常被静默忽略。

  2. 关闭线程池:使用完自定义线程池后,记得调用 shutdown() 方法关闭它。

  3. 避免过度使用:虽然 CompletableFuture 很强大,但不是所有场景都需要使用它。对于简单的异步任务,FutureTask 可能已经足够。

  4. 组合操作时注意依赖关系:使用 thenCompose 处理依赖关系,使用 thenCombine 或 allOf 处理独立任务。

  5. 使用 complete() 和 completeExceptionally() 手动控制:在某些场景下,可能需要手动完成 CompletableFuture 或使其失败。

七、总结

CompletableFuture 是 Java 并发编程中的一个强大工具,它提供了丰富的 API 来处理异步计算结果。通过 CompletableFuture,我们可以以声明式的方式组合、链接和处理异步操作,大大简化了异步编程的复杂性。

在实际应用中,CompletableFuture 常用于:

  • 并行处理独立任务
  • 构建复杂的异步工作流
  • 实现非阻塞的 API
  • 处理异步计算结果

掌握 CompletableFuture 的使用,对于提升 Java 并发编程能力和构建高性能应用程序具有重要意义。