CompletableFuture 源码深度解析:6 大 API + 链式编排实战

#java/并发 #难度/进阶 #类型/笔记

本文回答什么问题 :JDK 8 CompletableFuture 如何用链式 API 解决 Future.get() 阻塞 + 回调地狱,覆盖 6 大 API、4 种组合模式与异常处理最佳实践? 预计阅读时间 :20 分钟

源码版本 :OpenJDK 17(java.util.concurrent.CompletableFuture + CompletionStage

JDK 8 引入的 CompletableFuture 不仅是 Future 接口的实现,更是 Java 异步编程的事实标准。它把回调、组合、异常处理、超时控制全部串成链式 API,彻底告别回调地狱。本文用 6 个可执行 Demo + 4 张时序/流程图,把 supplyAsync / thenApply / thenCompose / thenCombine / exceptionally / orTimeout 这 6 大 API 一次性讲透。

核心要点速查(建议收藏)

  • JDK 8 引入 CompletableFuture,JDK 9+ 新增 orTimeout / completeOnTimeout 超时控制,目前生产推荐使用 JDK 17 LTS。
  • supplyAsync(Supplier)supplyAsync(Supplier, Executor) 共 4 种创建方式 ,必须指定自定义线程池,严禁依赖 ForkJoinPool.commonPool() (守护线程 + 线程数 = CPU-1 + 与 parallelStream 共享)。
  • thenApply 返回 CompletableFuture<CompletableFuture<T>>(嵌套) vs thenCompose 返回 CompletableFuture<T>(扁平化) ------有异步调用必须用 thenCompose
  • 组合 3 件套:thenCombine(并行汇合) / thenAcceptBoth(双消费) / applyToEither(竞争取最快) ------分别对应"等所有"、"消费所有"、"取最快"三种语义。
  • 异常处理 3 件套:exceptionally(兜底返回默认)/ handle(转换结果或异常)/ whenComplete(回调不改值) ------链式调用必须 exceptionally 兜底,否则异常会"吞噬"后续 stage。
  • CompletableFuture 本身线程安全,但任务内的共享状态必须用 AtomicInteger 等同步------本文 Demo 6 是经典的「订单+库存+用户」并行查询实战模板。

📋 概述

一句话:CompletableFuture 是 JDK 8 引入的"异步编排神器",让你像搭积木一样组合多个异步任务,告别 Future.get() 的阻塞等待。

为什么需要 CompletableFuture?

在 Java 中,我们经常需要同时执行多个异步任务(比如同时查询用户信息、订单详情、库存数据)。传统的 Future 接口有两个痛点:

  1. get() 会阻塞 :调用 future.get() 时,当前线程会一直等待直到任务完成,失去了异步的意义。
  2. 无法链式组合:多个异步任务之间有依赖关系时(比如先查用户 ID,再用用户 ID 查订单),代码会变成嵌套的回调地狱。

CompletableFuture 解决了这两个问题,它支持:

  • 非阻塞获取结果 :通过 thenApplythenAccept 等链式 API
  • 任务组合thenCombine(并行组合)、thenCompose(串行组合)
  • 异常处理exceptionallyhandlewhenComplete
  • 超时控制 :JDK 9+ 的 orTimeoutcompleteOnTimeout

💡 生活类比:外卖接力系统

把异步任务想象成外卖订单 ,CompletableFuture 就是外卖接力系统

外卖流程 异步任务 说明
下单 supplyAsync(() -> createOrder()) 创建异步任务
接单 thenApply(order -> pack(order)) 订单完成后打包
配送 thenAccept(packed -> deliver(packed)) 打包完成后配送
完成 thenRun(() -> notifyUser()) 通知用户

每个环节都是异步 的,可以串行 (下单→接单→配送)也可以并行(同时下单多个外卖)。

🔍 创建异步任务

CompletableFuture 提供 4 种创建方式:

方法 返回值 用途 是否指定线程池
supplyAsync(Supplier) CompletableFuture<T> 有返回值的异步任务 否(用 ForkJoinPool.commonPool())
supplyAsync(Supplier, Executor) CompletableFuture<T> 有返回值的异步任务
runAsync(Runnable) CompletableFuture<Void> 无返回值的异步任务
runAsync(Runnable, Executor) CompletableFuture<Void> 无返回值的异步任务

线程池选择

ini 复制代码
// ❌ 默认使用 ForkJoinPool.commonPool(),可能被 parallelStream 共享
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "hello");

// ✅ 指定自定义线程池,隔离业务
ExecutorService bizPool = Executors.newFixedThreadPool(8);
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "hello", bizPool);

为什么必须指定线程池?

  • ForkJoinPool.commonPool() 是守护线程,主线程结束时 JVM 可能退出
  • 线程数 = CPU 核数 - 1,IO 密集场景不够用
  • 与 parallelStream 共享,互相影响

🔍 链式转换

链式转换是 CompletableFuture 的核心能力,让异步任务像流水线一样串联。

thenApply vs thenAccept vs thenRun

方法 输入 输出 用途
thenApply(Function) T U 转换结果
thenAccept(Consumer) T void 消费结果
thenRun(Runnable) void 执行动作
arduino 复制代码
// thenApply:转换结果
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "hello");
CompletableFuture<Integer> lengthFuture = future.thenApply(String::length);  // 5

// thenAccept:消费结果(无返回值)
future.thenAccept(s -> System.out.println("结果: " + s));

// thenRun:执行动作(不关心前一步结果)
future.thenRun(() -> System.out.println("任务完成"));

thenCompose vs thenApply

arduino 复制代码
// thenApply:返回 CompletableFuture<CompletableFuture<T>>(嵌套)
CompletableFuture<CompletableFuture<String>> nested = future
    .thenApply(id -> queryUser(id));  // ❌ 嵌套了

// thenCompose:返回 CompletableFuture<T>(扁平化)
CompletableFuture<String> flat = future
    .thenCompose(id -> queryUser(id));  // ✅ 扁平化

关键区别

  • thenApply:同步转换,返回值是 CompletableFuture<U>
  • thenCompose:异步组合,返回值是 CompletableFuture<T>,适合链式异步调用

链式调用时序图

🔍 组合任务

CompletableFuture 支持多种任务组合模式:

thenCombine:两任务并行组合

ini 复制代码
CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() -> "张三");
CompletableFuture<String> orderFuture = CompletableFuture.supplyAsync(() -> "订单123");

// 两个任务都完成后组合结果
CompletableFuture<String> result = userFuture.thenCombine(orderFuture, 
    (user, order) -> user + "的" + order);

System.out.println(result.join());  // 张三的订单123

thenAcceptBoth / runAfterBoth

csharp 复制代码
// thenAcceptBoth:两个任务都完成后消费结果
userFuture.thenAcceptBoth(orderFuture, (user, order) -> {
    System.out.println("用户: " + user + ", 订单: " + order);
});

// runAfterBoth:两个任务都完成后执行动作
userFuture.runAfterBoth(orderFuture, () -> {
    System.out.println("两个任务都完成了");
});

applyToEither / acceptEither(竞争取最快)

ini 复制代码
CompletableFuture<String> source1 = CompletableFuture.supplyAsync(() -> {
    sleep(1000);
    return "来源1";
});

CompletableFuture<String> source2 = CompletableFuture.supplyAsync(() -> {
    sleep(500);
    return "来源2";
});

// 取最快完成的任务结果
String fastest = source1.applyToEither(source2, s -> "最快: " + s).join();
System.out.println(fastest);  // 最快: 来源2

组合模式流程图

🔍 异常处理

异步任务中异常处理至关重要,CompletableFuture 提供三种方式:

exceptionally:捕获异常,返回默认值

arduino 复制代码
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    if (true) throw new RuntimeException("出错了");
    return "正常结果";
});

CompletableFuture<String> safe = future.exceptionally(ex -> {
    System.out.println("捕获异常: " + ex.getMessage());
    return "默认值";  // 异常时返回默认值
});

System.out.println(safe.join());  // 默认值

handle:处理结果/异常,可改变返回值

kotlin 复制代码
CompletableFuture<String> handled = future.handle((result, ex) -> {
    if (ex != null) {
        return "异常处理: " + ex.getMessage();
    }
    return "正常: " + result;
});

System.out.println(handled.join());  // 异常处理: 出错了

whenComplete:完成回调,不改变结果

csharp 复制代码
future.whenComplete((result, ex) -> {
    if (ex != null) {
        System.out.println("任务失败: " + ex.getMessage());
    } else {
        System.out.println("任务成功: " + result);
    }
});
// 结果仍然是异常,whenComplete 不改变结果

异常处理时序图

🔍 CompletableFuture vs Future

特性 Future CompletableFuture
阻塞获取 get() 阻塞 join() 非阻塞(但会抛异常)
链式调用 ❌ 不支持 ✅ thenApply/thenAccept/thenRun
任务组合 ❌ 不支持 ✅ thenCombine/thenCompose
异常处理 ❌ 只能 try-catch ✅ exceptionally/handle/whenComplete
超时控制 get(timeout, unit) orTimeout/completeOnTimeout(JDK 9+)
手动完成 ❌ 不支持 ✅ complete/completeExceptionally
异步回调 ❌ 不支持 ✅ thenApplyAsync/thenAcceptAsync

💻 可跑代码

Demo 1:基本异步任务

arduino 复制代码
import java.util.concurrent.CompletableFuture;

public class BasicAsyncDemo {
    public static void main(String[] args) {
        // 创建异步任务
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            sleep(1000);
            return "Hello, CompletableFuture!";
        });
        
        // 非阻塞获取结果
        System.out.println("任务已提交,主线程继续执行...");
        
        // 方式1:join()(推荐,会抛CompletionException)
        String result = future.join();
        System.out.println("结果: " + result);
        
        // 方式2:get()(需要处理异常)
        // String result2 = future.get();  // 抛 InterruptedException, ExecutionException
    }
    
    static void sleep(long ms) {
        try { Thread.sleep(ms); } catch (InterruptedException e) { }
    }
}

预期输出

makefile 复制代码
任务已提交,主线程继续执行...
结果: Hello, CompletableFuture!

Demo 2:链式 thenApply + thenAccept

csharp 复制代码
import java.util.concurrent.CompletableFuture;

public class ChainDemo {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> {
            System.out.println("1. 查询用户ID");
            return 1001;
        })
        .thenApply(userId -> {
            System.out.println("2. 根据ID查询用户名,userId=" + userId);
            return "张三";
        })
        .thenAccept(userName -> {
            System.out.println("3. 打印用户名: " + userName);
        })
        .join();
    }
}

预期输出

markdown 复制代码
1. 查询用户ID
2. 根据ID查询用户名,userId=1001
3. 打印用户名: 张三

Demo 3:thenCombine 两任务组合

typescript 复制代码
import java.util.concurrent.CompletableFuture;

public class CombineDemo {
    public static void main(String[] args) {
        CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() -> {
            sleep(500);
            return "张三";
        });
        
        CompletableFuture<String> orderFuture = CompletableFuture.supplyAsync(() -> {
            sleep(300);
            return "订单123";
        });
        
        // 两个任务都完成后组合结果
        CompletableFuture<String> result = userFuture.thenCombine(orderFuture, 
            (user, order) -> user + "的" + order);
        
        System.out.println("组合结果: " + result.join());
    }
    
    static void sleep(long ms) {
        try { Thread.sleep(ms); } catch (InterruptedException e) { }
    }
}

预期输出

makefile 复制代码
组合结果: 张三的订单123

Demo 4:thenCompose 扁平化

csharp 复制代码
import java.util.concurrent.CompletableFuture;

public class ComposeDemo {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> {
            System.out.println("1. 获取用户ID");
            return 1001;
        })
        .thenCompose(userId -> {
            // thenCompose 避免了 CompletableFuture<CompletableFuture<T>> 嵌套
            System.out.println("2. 根据ID查询用户详情,userId=" + userId);
            return CompletableFuture.supplyAsync(() -> "用户: 张三, ID: " + userId);
        })
        .thenAccept(userDetail -> {
            System.out.println("3. 打印详情: " + userDetail);
        })
        .join();
    }
}

预期输出

markdown 复制代码
1. 获取用户ID
2. 根据ID查询用户详情,userId=1001
3. 打印详情: 用户: 张三, ID: 1001

Demo 5:exceptionally 异常处理

arduino 复制代码
import java.util.concurrent.CompletableFuture;

public class ExceptionDemo {
    public static void main(String[] args) {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("执行任务...");
            if (true) throw new RuntimeException("任务执行失败!");
            return "正常结果";
        })
        .exceptionally(ex -> {
            System.out.println("捕获异常: " + ex.getMessage());
            return "默认值";
        });
        
        System.out.println("结果: " + future.join());
    }
}

预期输出

makefile 复制代码
执行任务...
捕获异常: java.lang.RuntimeException: 任务执行失败!
结果: 默认值

Demo 6:实战 - 异步查询订单+库存+用户

ini 复制代码
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class OrderQueryDemo {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        
        // 三个独立查询并行执行
        CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() -> {
            sleep(300);
            return "张三";
        }, pool);
        
        CompletableFuture<String> orderFuture = CompletableFuture.supplyAsync(() -> {
            sleep(200);
            return "订单#123";
        }, pool);
        
        CompletableFuture<Integer> stockFuture = CompletableFuture.supplyAsync(() -> {
            sleep(400);
            return 10;
        }, pool);
        
        // 等待全部完成后组合结果
        CompletableFuture<String> result = CompletableFuture.allOf(userFuture, orderFuture, stockFuture)
            .thenApply(v -> {
                String user = userFuture.join();
                String order = orderFuture.join();
                int stock = stockFuture.join();
                return String.format("用户: %s, 订单: %s, 库存: %d", user, order, stock);
            });
        
        System.out.println("查询结果: " + result.join());
        pool.shutdown();
    }
    
    static void sleep(long ms) {
        try { Thread.sleep(ms); } catch (InterruptedException e) { }
    }
}

预期输出

bash 复制代码
查询结果: 用户: 张三, 订单: 订单#123, 库存: 10

⚠️ 常见问题与踩坑

Q1: get() 会阻塞吗?用哪个方法更好?

A: get() 会阻塞当前线程直到任务完成。推荐使用 join()

  • join():不抛 checked exception,抛 CompletionException
  • get():抛 InterruptedExceptionExecutionException
arduino 复制代码
// ❌ 阻塞且需要处理 checked exception
try {
    String result = future.get();
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

// ✅ 推荐:不阻塞(异步场景),异常包装为 CompletionException
String result = future.join();

Q2: 线程池用哪个?ForkJoinPool.commonPool vs 自定义?

A: 必须使用自定义线程池,原因:

  1. ForkJoinPool.commonPool() 是守护线程,主线程结束时 JVM 可能退出
  2. 线程数 = CPU 核数 - 1,IO 密集场景不够用
  3. 与 parallelStream 共享,互相影响
scss 复制代码
// ❌ 默认公共池
CompletableFuture.supplyAsync(() -> biz());

// ✅ 自定义线程池
ExecutorService bizPool = Executors.newFixedThreadPool(8);
CompletableFuture.supplyAsync(() -> biz(), bizPool);

Q3: 异常丢失怎么办?

A: 链式调用中,异常会"吞噬"后续 stage。必须用 exceptionallyhandle 兜底:

php 复制代码
// ❌ 异常丢失:后续 thenApply 不执行
CompletableFuture.supplyAsync(() -> {
    throw new RuntimeException("出错");
})
.thenApply(r -> "正常")  // 不执行
.thenAccept(System.out::println);  // 不执行

// ✅ 异常处理:exceptionally 兜底
CompletableFuture.supplyAsync(() -> {
    throw new RuntimeException("出错");
})
.thenApply(r -> "正常")
.exceptionally(ex -> "默认值")  // 捕获异常
.thenAccept(System.out::println);  // 执行

Q4: 超时怎么控制?

A: JDK 9+ 提供两种方式:

rust 复制代码
// 方式1:orTimeout - 超时抛异常
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    sleep(5000);
    return "结果";
}).orTimeout(2, TimeUnit.SECONDS);  // 2秒后抛 TimeoutException

// 方式2:completeOnTimeout - 超时用默认值
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
    sleep(5000);
    return "结果";
}).completeOnTimeout("默认值", 2, TimeUnit.SECONDS);  // 2秒后返回默认值

Q5: CompletableFuture 线程安全吗?

A: CompletableFuture 本身是线程安全的 ,但任务中的共享状态不是

scss 复制代码
// ❌ 共享状态不是线程安全
int[] count = {0};
CompletableFuture.runAsync(() -> count[0]++);
CompletableFuture.runAsync(() -> count[0]++);
// 结果可能是 1 或 2,不确定

// ✅ 使用原子类
AtomicInteger atomicCount = new AtomicInteger(0);
CompletableFuture.runAsync(() -> atomicCount.incrementAndGet());
CompletableFuture.runAsync(() -> atomicCount.incrementAndGet());
// 结果一定是 2

🎯 最佳实践

Do / Don't

✅ Do ❌ Don't
指定自定义线程池 使用默认 ForkJoinPool.commonPool()
用 join() 替代 get() 在异步上下文中调用 get()
异常处理兜底 忽略异常导致任务丢失
超时控制 无限等待任务完成
扁平化用 thenCompose thenApply 返回嵌套 CompletableFuture

实战模式表

模式 场景 示例
串行执行 任务有依赖关系 thenCompose
并行执行 独立任务同时进行 supplyAsync + thenCombine
竞争取最快 多源冗余 applyToEither
全部完成 汇总多个任务结果 allOf
任一完成 取最快结果 anyOf

💡 面试要点

  1. CompletableFuture 与 Future 的区别?

    • Future 只能阻塞获取结果,CompletableFuture 支持链式调用、任务组合、异常处理
  2. thenApply 和 thenCompose 的区别?

    • thenApply 用于同步转换,thenCompose 用于异步组合(避免嵌套)
  3. 为什么必须指定线程池?

    • 默认 ForkJoinPool.commonPool() 是守护线程,可能被 parallelStream 共享,线程数不够
  4. 如何处理异步任务中的异常?

    • exceptionally:捕获异常返回默认值
    • handle:处理结果/异常,可改变返回值
    • whenComplete:完成回调,不改变结果
  5. CompletableFuture 线程安全吗?

    • 本身线程安全,但任务中的共享状态需要额外同步(用原子类或锁)
  6. 如何控制超时?

    • JDK 9+:orTimeout(抛异常)、completeOnTimeout(返回默认值)
    • JDK 8:future.get(timeout, unit)
  7. allOf 和 anyOf 的区别?

    • allOf:所有任务完成后返回 Void
    • anyOf:任一任务完成后返回结果

📝 总结

要点 记住这一句
核心价值 链式调用 + 任务组合 + 异常处理
创建任务 supplyAsync(有返回值)、runAsync(无返回值)
链式转换 thenApply(转换)、thenAccept(消费)、thenRun(执行)
任务组合 thenCombine(并行)、thenCompose(串行)、applyToEither(竞争)
异常处理 exceptionally(兜底)、handle(转换)、whenComplete(回调)
线程池 必须指定自定义线程池,避免 commonPool()
超时控制 JDK 9+ 用 orTimeout/completeOnTimeout

📖 外部参考


本文要点速查

  1. CompletableFuture = Future + 回调 + 组合 + 异常处理 + 超时控制 的"全家桶",JDK 8 引入、JDK 9+ 增强。
  2. 链式 4 件套:supplyAsyncthenApply/thenComposethenCombine/applyToEitherjoin() ,异步任务像流水线一样串联。
  3. 3 个红线:必须自定义线程池 / 必须 exceptionally 兜底 / 必须区分 thenApplythenCompose------踩任意一个就上线翻车。

tags :#java #并发 #CompletableFuture #异步编程 #Future #链式编排 #JDK17 #源码解析

相关推荐
Zane19941 小时前
map 比推导式快"是真的吗?一文讲透 map、filter、reduce 与 lambda 的真实性能与设计取舍
后端·python
字节跳动数据库1 小时前
火山引擎 RDS MySQL 向量索引:把高性能向量检索带到 MySQL 上
人工智能·后端·mysql
拖孩1 小时前
用 AI 重解千年观音灵签,做了一个微信小程序,每天摇一摇,命运给你回应
前端·后端·微信小程序
Python私教1 小时前
AI Agent 可观测性不只是日志:一套可回放的多步执行链
人工智能·后端·python
Python私教2 小时前
模型越强越不需要 Skills?我把 AI 编程能力拆成 4 层
人工智能·后端·python
名字还没想好☜3 小时前
Go 的 io.Reader/Writer 组合实战:io.Copy、TeeReader、MultiWriter 优雅处理数据流
开发语言·后端·golang·go·iphone
喵个咪3 小时前
GoWind Shop 架构剖析:一个 REST 请求穿越三服务 BFF 的七环链路
vue.js·后端·go
喵个咪3 小时前
GoWind Shop 安全设计:JWT 鉴权、Ent 行级隔离、防篡改审计与四个真实漏洞修复
vue.js·后端·go
喵个咪3 小时前
GoWind Shop:一个 proto 文件如何生成后端、前端、文档、校验六路代码
vue.js·后端·go