Java异步编程:CompletableFuture从入门到底层实现

大家好,今天我来给大家讲解一下CompletableFuture,从基础使用到底层实现


前言

在 Java 的并发编程演进史中,Future 的出现是一个里程碑,它允许我们异步获取计算结果。然而,Java 5 引入的 Future 存在显著的局限性:它很难在不阻塞主线程的情况下组合多个异步任务,且缺乏优雅的回调机制。

一、CompletableFuture是什么?

前言提到Future在异步处理计算结果的功能上存在局限性,需要阻塞主线程才能组合多个异步结果,并且没有优雅的回调机制,为了解决这个问题,JDK1.8引入了CompletableFuture。

Java 8 引入了神器 CompletableFuture。它实现了 FutureCompletionStage 接口,不仅提供了强大的函数式编程能力,还构建了一套完整的异步任务编排体系

二、CompletableFuture的基本使用

2.1 异步任务的发起

通常异步任务不需要我们手动new,直接使用CompletableFuture的工厂方法即可

java 复制代码
// 1. 无返回值的异步任务 (runAsync)
CompletableFuture<Void> runFuture = CompletableFuture.runAsync(() -> {
    System.out.println("执行无返回值的异步任务: " + Thread.currentThread().getName());
});

// 2. 有返回值的异步任务 (supplyAsync)
CompletableFuture<String> supplyFuture = CompletableFuture.supplyAsync(() -> {
    System.out.println("执行有返回值的异步任务: " + Thread.currentThread().getName());
    return "Hello World";
});

对于提交的任务会提交到ForkJoinPool.CommonPool()去异步执行。

2.2 链式流转:thenApply, thenAccept, thenRun

任务完成后的回调,根据参数和返回值的不同分为三类:

  • thenApply (变换): 拿到上一步结果,处理后返回新结果(Function: T -> R)。

  • thenAccept (消费): 拿到上一步结果,处理后不返回(Consumer: T -> Void)。

  • thenRun (触发): 不关心上一步结果,只是触发下一步(Runnable: Void -> Void)。

java 复制代码
CompletableFuture.supplyAsync(() -> "Java")
    .thenApply(s -> s + " 8")           // 变换: "Java" -> "Java 8"
    .thenAccept(s -> System.out.println("Result: " + s)) // 消费
    .thenRun(() -> System.out.println("Finished!"));     // 触发

2.3 任务组合

CompletableFuture可以很好的避免回调地狱,并且代码简洁可读性高

2.3.1 两个独立任务的组合 (AND 关系) -> thenCombine

当任务 A 和任务 B 都完成后,执行任务 C。

java 复制代码
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
    // 模拟耗时
    return 10;
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
    return 20;
});

// 合并结果: 10 + 20
CompletableFuture<Integer> result = future1.thenCombine(future2, (res1, res2) -> res1 + res2);

2.3.2 两个前后依赖任务的组合 (串联) -> thenCompose

类似于 Stream 流中的 flatMap。通过上一个任务的结果,生成下一个 CompletableFuture

java 复制代码
CompletableFuture<String> result = CompletableFuture.supplyAsync(() -> "User123")
    .thenCompose(userId -> {
        // 使用 userId 去查询详细信息,返回一个新的 Future
        return UserService.getUserDetailAsync(userId); 
    });

2.3.3 多任务聚合 -> allOf / anyOf

  • allOf: 等待所有任务完成(常用于并行查询多个接口,最后统一组装)。

  • anyOf: 只要有一个任务完成就返回(常用于备份链路查询,谁快用谁)。

java 复制代码
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

public class AllOfExample {

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        System.out.println("--- 详情页数据加载开始 ---");

        // 1. 异步任务:获取商品基本信息
        CompletableFuture<String> infoFuture = CompletableFuture.supplyAsync(() -> {
            sleep(1); // 模拟耗时
            System.out.println("商品信息查询完成");
            return "iPhone 15 Pro";
        });

        // 2. 异步任务:获取评论数据
        CompletableFuture<Integer> commentFuture = CompletableFuture.supplyAsync(() -> {
            sleep(2);
            System.out.println("评论统计查询完成");
            return 9999;
        });

        // 3. 异步任务:获取推荐商品
        CompletableFuture<String> recommendFuture = CompletableFuture.supplyAsync(() -> {
            sleep(3);
            System.out.println("推荐商品查询完成");
            return "AirPods Pro 2";
        });

        // 4. allOf 编排:等待所有任务完成
        // 注意:allOf 返回的是 CompletableFuture<Void>,它不持有结果
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(infoFuture, commentFuture, recommendFuture);

        // 5. 等待完成并提取结果
        // 这里使用 join() 阻塞主线程,直到 allFutures 完成
        allFutures.join(); 

        // 6. 此时三个任务肯定都完成了,可以直接 get/join 获取结果,不会再阻塞
        String info = infoFuture.join();
        Integer comments = commentFuture.join();
        String recommend = recommendFuture.join();

        long endTime = System.currentTimeMillis();
        System.out.println("--- 详情页加载完毕 ---");
        System.out.println("总耗时: " + (endTime - startTime) / 1000 + "秒");
        System.out.println("结果聚合: [" + info + ", 评论数:" + comments + ", 推荐:" + recommend + "]");
    }

    private static void sleep(int seconds) {
        try { TimeUnit.SECONDS.sleep(seconds); } catch (InterruptedException e) { e.printStackTrace(); }
    }
}
java 复制代码
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

public class AnyOfExample {

    public static void main(String[] args) {
        System.out.println("--- 开始双路查询 ---");

        // 1. 任务A:快线路 (模拟 1秒)
        CompletableFuture<String> fastService = CompletableFuture.supplyAsync(() -> {
            sleep(1);
            System.out.println("Fast Service 响应");
            return "来自快速服务的数据";
        });

        // 2. 任务B:慢线路 (模拟 3秒)
        CompletableFuture<String> slowService = CompletableFuture.supplyAsync(() -> {
            sleep(3);
            System.out.println("Slow Service 响应");
            return "来自慢速服务的数据";
        });

        // 3. anyOf 竞速
        // 只要有一个完成(无论是正常完成还是异常),anyOf 就会完成
        CompletableFuture<Object> winnerFuture = CompletableFuture.anyOf(fastService, slowService);

        // 4. 获取结果
        // 注意:anyOf 返回的是 Object,因为参与竞速的任务可能有不同的返回类型
        Object result = winnerFuture.join();

        System.out.println("最终采纳结果: " + result);
    }
    
    private static void sleep(int seconds) {
        try { TimeUnit.SECONDS.sleep(seconds); } catch (InterruptedException e) { e.printStackTrace(); }
    }
}

2.3.4 优雅的异常处理

不再需要 try-catch 包裹整个逻辑块:

  • exceptionally: 仅当发生异常时触发,提供降级值。

  • handle: 无论成功失败都会触发,可以同时访问结果和异常。

java 复制代码
CompletableFuture.supplyAsync(() -> {
    if (true) throw new RuntimeException("DB Error");
    return "Success";
}).exceptionally(ex -> {
    System.out.println("发生异常: " + ex.getMessage());
    return "Default Value"; // 降级策略
});

三、底层实现

3.1. 核心数据结构

CompletableFuture 内部维护了两个核心关键点:

  1. result (volatile Object): 存储任务的执行结果或异常。

    • 如果任务未完成,它是 null(或者特定的 ALT 标记)。

    • 如果任务完成,它是具体的值。

    • 如果异常,它被封装为 AltResult

  2. stack (volatile Completion) : 这是实现链式调用的关键。它是一个无锁的 Treiber Stack(驱动栈)

3.2. Completion 对象:观察者模式的变体

当你调用 future.thenApply(...) 时,由于前一个任务可能还没完成,主线程不会阻塞等待。相反,它会将你的回调逻辑封装成一个 Completion 对象(继承自 ForkJoinTask),然后通过 CAS 操作压入 stack 栈顶。

每一个 Completion 对象都包含:

  • Executor: 该回调将在哪个线程池执行。

  • Dep : 下一步产生的新的 CompletableFuture

  • Src : 当前依赖的上游 CompletableFuture

  • Action: 具体的执行逻辑(如 Function, Consumer)。

3.3. 状态流转与通知机制 (Observer Pattern + CAS)

当上游任务执行完毕(调用 complete()completeExceptionally())时,会触发以下流程:

  1. CAS 更新状态 : 使用 Unsafe.compareAndSwapObjectresult 字段从 null 更新为结果值。

  2. 触发回调 (postComplete): 这是核心驱动引擎。

    • 当前线程会检测 stack 是否为空。

    • 如果不为空,通过 CAS 弹出栈顶的 Completion 对象。

    • 执行该对象的 tryFire() 方法。

    • tryFire() 内部会执行用户的回调逻辑,并将结果填充到下一个 CompletableFuture 中,从而产生连锁反应(Domino Effect)。

也就是说我们的CompletableFuture里面维护了一个Completion链,这个链子上有多个Completion,每个Completion代表一个异步任务,并且每个Completion可以依赖它的前后任务的结果,对于提交的任务,一般交给Java的共有的ForkJoinPool的线程去异步执行。

当一个Completion阶段执行完后CompletableFuture会去触发它的下一个与之关联的Completion阶段。

总结

CompletableFuture提供了一组简洁实现异步、任务组合关联的API,这在我们的代码使用可以以可读性高的代码表示含义!

相关推荐
xiaomin-Michael2 小时前
netty学习
java
九.九2 小时前
高性能算子库 ops-nn 的底层架构:从调度到指令的极致优化
开发语言
比奇堡派星星2 小时前
sed命令
linux·运维·服务器·开发语言
船神丿男人2 小时前
C++:STL string(一)
开发语言·c++
上海合宙LuatOS2 小时前
LuatOS核心库API——【fft 】 快速傅里叶变换
java·前端·人工智能·单片机·嵌入式硬件·物联网·机器学习
程序员zgh2 小时前
Linux 内存管理单元 MMU
linux·运维·服务器·c语言·开发语言·c++
爱敲代码的小鱼2 小时前
web后端开发SpringBootWeb的入门:
java·spring boot·spring
想做功的洛伦兹力13 小时前
2026/2/12日打卡
开发语言·c++·算法
大模型玩家七七3 小时前
技术抉择:微调还是 RAG?——以春节祝福生成为例
android·java·大数据·开发语言·人工智能·算法·安全