CompletableFuture 使用总结

一、核心概念

CompletableFuture 是 Java 8 引入的一个类,实现了 FutureCompletionStage 接口。

  1. Future: 代表一个异步计算的结果。提供了检查计算是否完成 (isDone())、等待计算完成 (get()) 等方法。但获取结果会阻塞线程。
  2. CompletionStage: 代表异步计算的一个阶段步骤。它定义了当当前阶段完成时,可以触发执行一系列后续操作(如 thenApply, thenAccept, thenRun 等)。CompletableFuture 的核心能力正源于此。
  3. 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();
        });

六、线程池与控制

  • 默认线程池runAsyncsupplyAsync 的默认执行器是 ForkJoinPool.commonPool()在生产环境中,强烈建议使用自定义线程池,以避免公共线程池被阻塞任务影响。
  • 控制执行线程
    • thenApply/thenAccept/thenRun 等默认在完成上一个任务的同一个线程中执行,或者由完成回调的线程执行(可能不是异步的)。
    • 使用 thenApplyAsyncthenAcceptAsyncthenRunAsync 等带 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 -> { ... });

七、最佳实践与注意事项

  1. 总是使用自定义线程池: 避免使用默认的 ForkJoinPool.commonPool(),尤其是进行 I/O 操作时,防止阻塞其他重要任务。
  2. 谨慎使用 get()get() 方法是阻塞的,会违背使用异步的初衷。尽量使用 thenAcceptthenApply 等回调方法来处理最终结果。如果必须阻塞获取,请使用带超时参数的 get(long timeout, TimeUnit unit)
  3. 注意异常处理: 一定要使用 exceptionallyhandle 等方法处理异常,否则异常可能会被悄无声息地吞掉,难以调试。
  4. 避免深度嵌套: 使用 thenCompose 来展平嵌套的 CompletableFuture,保持代码的可读性。
  5. 理解 *Async 方法: 明确 thenApplythenApplyAsync 的区别。前者可能在回调线程执行,后者保证在新的异步线程中执行。
  6. 超时控制 (JDK9+): JDK9 引入了 orTimeoutcompleteOnTimeout 方法来方便地设置超时。
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        // long running task
    }).orTimeout(3, TimeUnit.SECONDS) // 3秒后如果未完成,则异常完成
     .exceptionally(ex -> "Fallback due to timeout: " + ex.getMessage());
    

总结

CompletableFuture 是 Java 进行异步编程的强大工具,它将 Promise 模式和函数式编程完美结合。通过熟练运用其各种组合子和异常处理方法,你可以优雅地构建出高效、清晰且健壮的异步应用程序流水线。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

思静鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值