一、核心概念
CompletableFuture 是 Java 8 引入的一个类,实现了 Future 和 CompletionStage 接口。
Future: 代表一个异步计算的结果。提供了检查计算是否完成 (isDone())、等待计算完成 (get()) 等方法。但获取结果会阻塞线程。CompletionStage: 代表异步计算的一个阶段或步骤。它定义了当当前阶段完成时,可以触发执行一系列后续操作(如 thenApply, thenAccept, thenRun 等)。CompletableFuture的核心能力正源于此。CompletableFuture: 结合了两者。它既是一个Future,也是一个CompletionStage。它不仅代表一个异步结果,更重要的是,它允许你以非阻塞的方式构建异步操作的流水线,将多个异步任务组合成一个复杂的、非阻塞的计算流程。
二、创建 CompletableFuture
1. 运行简单的异步任务
// 使用默认的 ForkJoinPool.commonPool() 执行
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
System.out.println("运行一个无返回值的异步任务");
});
// 使用自定义线程池执行
ExecutorService customExecutor = Executors.newCachedThreadPool();
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
System.out.println("在自定义线程池中运行");
}, customExecutor);
// 有返回值的异步任务
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
return "异步任务的结果";
});
CompletableFuture<String> future4 = CompletableFuture.supplyAsync(() -> {
return "在自定义线程池中获取结果";
}, customExecutor);
2. 创建一个已完成或已失败的 Future
// 创建一个已经正常完成并带有给定值的 Future
CompletableFuture<String> completedFuture = CompletableFuture.completedFuture("Hello, World!");
// 创建一个已失败的 Future (JDK9+)
CompletableFuture<String> failedFuture = CompletableFuture.failedFuture(new RuntimeException("Oops!"));
// JDK8 创建已失败 Future 的方式
CompletableFuture<String> failedFuture8 = new CompletableFuture<>();
failedFuture8.completeExceptionally(new RuntimeException("Oops!"));
三、结果处理与转换(核心方法)
这些方法是构建流水线的基石。它们都会返回一个新的 CompletableFuture。
1. 转换结果:thenApply (类似 Map)
当前阶段正常完成后,对其结果执行一个同步函数,并返回一个带有函数返回值的新阶段。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "hello");
CompletableFuture<String> greetingFuture = future.thenApply(s -> s + " world");
greetingFuture.thenAccept(System.out::println); // 输出 "hello world"
// 链式调用
CompletableFuture.supplyAsync(() -> "hello")
.thenApply(s -> s + " world")
.thenApply(String::toUpperCase)
.thenAccept(System.out::println); // 输出 "HELLO WORLD"
2. 消费结果:thenAccept (类似 for-each)
当前阶段正常完成后,对其结果执行一个同步的消费操作(无返回值)。
CompletableFuture.supplyAsync(() -> "data")
.thenAccept(data -> System.out.println("Processing: " + data));
3. 执行动作:thenRun
当前阶段正常完成后,执行一个不依赖前阶段结果的 Runnable。
CompletableFuture.supplyAsync(() -> "data")
.thenRun(() -> System.out.println("计算完成,可以执行清理工作"));
4. 组合两个依赖的异步任务:thenCompose (类似 FlatMap)
当第一个异步任务完成后,将其结果作为参数传递给另一个异步任务(另一个 CompletableFuture),并返回最终组合后的结果。用于避免嵌套的 CompletableFuture。
// 假设 getUserById 和 getUserDetail 都返回 CompletableFuture
CompletableFuture<User> getUserById(String id) {
return CompletableFuture.supplyAsync(() -> userRepository.findById(id));
}
CompletableFuture<String> getUserDetail(User user) {
return CompletableFuture.supplyAsync(() -> user.getDetail());
}
// 错误做法:会产生 CompletableFuture<CompletableFuture<String>>
CompletableFuture<CompletableFuture<String>> badFuture = getUserById("1").thenApply(user -> getUserDetail(user));
// 正确做法:使用 thenCompose
CompletableFuture<String> resultFuture = getUserById("1")
.thenCompose(user -> getUserDetail(user)); // 最终结果是 CompletableFuture<String>
5. 组合两个独立的异步任务:thenCombine
当两个独立的 CompletableFuture 都完成后,使用一个 BiFunction 对它们的结果进行处理,返回一个新的结果。
CompletableFuture<Integer> priceFuture = getPriceAsync();
CompletableFuture<Double> discountFuture = getDiscountAsync();
CompletableFuture<Double> finalPriceFuture = priceFuture.thenCombine(discountFuture,
(price, discount) -> price * discount);
finalPriceFuture.thenAccept(finalPrice -> System.out.println("Final price: " + finalPrice));
四、多任务组合
1. allOf - 等待所有任务完成
等待多个 CompletableFuture 全部完成。它返回一个 CompletableFuture<Void>,不包含各个任务的结果。如果需要结果,需要手动从各个 Future 中获取。
CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> "Result1");
CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> "Result2");
CompletableFuture<String> task3 = CompletableFuture.supplyAsync(() -> "Result3");
CompletableFuture<Void> allFutures = CompletableFuture.allOf(task1, task2, task3);
// 在所有任务完成后执行操作
allFutures.thenRun(() -> {
// 这里可以安全地调用 task1.getNow(null) 等非阻塞方法获取结果
try {
String result1 = task1.get();
String result2 = task2.get();
String result3 = task3.get();
System.out.println("All results: " + result1 + ", " + result2 + ", " + result3);
} catch (Exception e) {
// handle exception
}
});
2. anyOf - 等待任意一个任务完成
等待多个 CompletableFuture 中的任意一个完成。返回一个新的 CompletableFuture<Object>,其结果是第一个完成的任务的结果。
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
return "Result from Future 1";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Result from Future 2");
CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(future1, future2);
anyFuture.thenAccept(result -> System.out.println("The first result is: " + result));
// 输出: "The first result is: Result from Future 2"
五、异常处理
CompletableFuture 提供了非常灵活的异常处理方式。
1. exceptionally - 捕获异常并提供默认值
类似于 try-catch,当流水线中之前的任何阶段抛出异常时,会触发此方法,并返回一个替代的默认值。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (true) throw new RuntimeException("Calculation error!");
return "Success";
}).exceptionally(ex -> {
System.out.println("Exception occurred: " + ex.getMessage());
return "Default Value"; // 从异常中恢复,返回一个默认值
});
// future.get() 会得到 "Default Value"
2. handle - 无论成功或失败都会处理
无论之前的阶段是正常完成还是异常完成,handle 方法都会被调用。它接收两个参数:成功的结果和异常。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
.handle((result, ex) -> {
if (ex != null) {
return "Handled exception: " + ex.getMessage();
}
return result.toUpperCase();
});
六、线程池与控制
- 默认线程池:
runAsync和supplyAsync的默认执行器是ForkJoinPool.commonPool()。在生产环境中,强烈建议使用自定义线程池,以避免公共线程池被阻塞任务影响。 - 控制执行线程:
thenApply/thenAccept/thenRun等默认在完成上一个任务的同一个线程中执行,或者由完成回调的线程执行(可能不是异步的)。- 使用
thenApplyAsync、thenAcceptAsync、thenRunAsync等带Async后缀的方法,可以强制让后续阶段在另一个线程(默认是ForkJoinPool或自定义的Executor)中异步执行。
ExecutorService ioBoundExecutor = Executors.newFixedThreadPool(10);
CompletableFuture.supplyAsync(() -> queryDatabase(), ioBoundExecutor) // 在IO线程池中查询
.thenApplyAsync(data -> processData(data)) // 在ForkJoinPool中处理CPU密集型任务
.thenAcceptAsync(result -> saveToDB(result), ioBoundExecutor) // 回到IO线程池保存
.exceptionally(ex -> { ... });
七、最佳实践与注意事项
- 总是使用自定义线程池: 避免使用默认的
ForkJoinPool.commonPool(),尤其是进行 I/O 操作时,防止阻塞其他重要任务。 - 谨慎使用
get():get()方法是阻塞的,会违背使用异步的初衷。尽量使用thenAccept、thenApply等回调方法来处理最终结果。如果必须阻塞获取,请使用带超时参数的get(long timeout, TimeUnit unit)。 - 注意异常处理: 一定要使用
exceptionally或handle等方法处理异常,否则异常可能会被悄无声息地吞掉,难以调试。 - 避免深度嵌套: 使用
thenCompose来展平嵌套的CompletableFuture,保持代码的可读性。 - 理解
*Async方法: 明确thenApply和thenApplyAsync的区别。前者可能在回调线程执行,后者保证在新的异步线程中执行。 - 超时控制 (JDK9+): JDK9 引入了
orTimeout和completeOnTimeout方法来方便地设置超时。CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { // long running task }).orTimeout(3, TimeUnit.SECONDS) // 3秒后如果未完成,则异常完成 .exceptionally(ex -> "Fallback due to timeout: " + ex.getMessage());
总结
CompletableFuture 是 Java 进行异步编程的强大工具,它将 Promise 模式和函数式编程完美结合。通过熟练运用其各种组合子和异常处理方法,你可以优雅地构建出高效、清晰且健壮的异步应用程序流水线。
1243

被折叠的 条评论
为什么被折叠?



