1. 概述

本教程将深入分析 Java 中 CompletableFuture 与 Project Reactor 的 Mono 的核心差异。我们将重点关注它们在异步任务处理机制和执行模型上的区别。

首先从 CompletableFuture 开始解析。

2. 理解 CompletableFuture

Java 8 引入的 CompletableFuture 在传统 Future 基础上进行了增强,提供了更强大的异步编程能力。它通过 thenApply()thenAccept()thenCompose() 等方法构建异步计算链,简化了线程协调工作。

⚠️ 关键注意点:虽然 CompletableFuture 是异步的(主线程不会等待操作完成),但它并非完全非阻塞。长时间运行的操作会阻塞执行线程:

CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(1000); // 模拟耗时操作
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    return "Finished completableFuture";
});

上述代码中,supplyAsync() 默认使用 ForkJoinPool 分配线程执行 Lambda,而 sleep() 会直接阻塞该线程。

更严重的问题:在操作完成前调用 get() 方法会阻塞主线程:

try {
    completableFuture.get(); // 主线程被阻塞
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

解决方案:通过回调模式手动处理完成状态:

public void myAsyncCall(String param, BiConsumer<String, Throwable> callback) {
    new Thread(() -> {
        try {
            Thread.sleep(1000);
            callback.accept("Response from API with param: " + param, null);
        } catch (InterruptedException e) {
            callback.accept(null, e);
        }
    }).start();
}

public CompletableFuture<String> nonBlockingApiCall(String param) {
    CompletableFuture<String> completableFuture = new CompletableFuture<>();
    myAsyncCall(param, (result, error) -> {
        if (error != null) {
            completableFuture.completeExceptionally(error);
        } else {
            completableFuture.complete(result);
        }
    });
    return completableFuture;
}

3. Mono 与 CompletableFuture 的对比

Project Reactor 的 Mono 采用响应式编程范式,与 CompletableFuture 相比具有两大核心优势:

3.1 资源效率

  • Mono 以更低开销实现并发
  • 采用懒加载机制:只有订阅时才会执行操作
  • 默认非阻塞设计
Mono<String> reactiveMono = Mono.fromCallable(() -> {
    Thread.sleep(1000); // 模拟计算
    return "Reactive Data";
}).subscribeOn(Schedulers.boundedElastic());

reactiveMono.subscribe(System.out::println);

3.2 线程调度机制

Schedulers.boundedElastic() 的特性:

  • 类似缓存线程池
  • ✅ 限制最大线程数
  • ✅ 防止资源耗尽
  • ⚠️ 唯一阻塞方式:强制调用 block() 方法

3.3 响应式特性

  • 丰富的操作符链(map, flatMap, filter 等)
  • ✅ 内置背压(backpressure)支持
  • ✅ 声明式组合多个异步操作

4. 结论

本文对比分析了 CompletableFutureMono 的核心差异:

特性 CompletableFuture Mono
执行模型 热加载(立即执行) 懒加载(订阅时执行)
阻塞风险 高(需手动避免阻塞) 低(默认非阻塞)
线程管理 依赖 ForkJoinPool 灵活的调度器
背压支持 ❌ 无 ✅ 内置支持
操作符丰富度 基础链式调用 完整响应式操作符

推荐场景

  • 简单异步任务 → CompletableFuture
  • 复杂响应式流 → Mono

完整代码示例可在 GitHub 获取。


原始标题:CompletableFuture vs. Mono | Baeldung