Java 异步编程:CompletableFuture 与 ForkJoin

简介: Java 异步编程绕不开 CompletableFuture 和 ForkJoin。用「回调」与「分治」两条主线串起来,附可运行示例,帮你分清何时用哪个。

大家好,我是晚安code。

学 Java 异步编程绕不开两个类:CompletableFuture 和 ForkJoin。我一开始先碰上 CompletableFuture,觉得「回调真香」;学到 ForkJoin,又觉得「分治好强」。可这俩都姓 Future,看着都在给并发提速,到底啥区别?好家伙,并发这条线是真学不完。

今天我把它们放一起讲:一个管「等」,一个管「算」。看完你就能分清什么时候该用哪个,代码都是能直接跑的示例。先收藏,我们开始。

一、为什么两个类都姓 Future

CompletableFuture 和 ForkJoin 都源自同一个接口:Future。这不是巧合,而是同一棵并发树上的两个分支——想搞懂 Java 异步编程,第一步就是认清它们共同的根。

Future:对「将来某个时刻才就绪的结果」建模的接口。你可以理解为一张「远期订单的存根」——结果还没到,但你手里已经有个凭据,可以等它、问它、取它。

早期的 Java 用 Future 做异步,但接口太简陋:想拿结果只能 get() 阻塞等,等到花儿都谢了。JDK 后来干脆做了两件大事,各自长出一条主线——

CompletableFuture:Java 8 引入的异步编程工具,实现了 CompletionStage 和 Future 两个接口,用回调把「结果还没好」和「结果好了怎么处理」彻底解耦。你可以理解为「外卖平台的实时订单」——出餐了自动通知你,不用在店门口干等。

ForkJoin 框架:JDK 7 引入的并行计算框架,把大任务递归拆成小任务并行执行,再合并结果(分治思想)。你可以理解为「组长把大活拆给全组,各自算完汇总」。

两条主线分工非常清晰:CompletableFuture 优化的是「等」——结果没就绪,我不阻塞、挂回调;ForkJoin 优化的是「算」——任务太大,我拆成小块并行算。看这张对比图,一眼记住:

Java 异步编程两大流派对比:CompletableFuture 异步回调链与 ForkJoin 分治并行,看清「等」与「算」两条处理路径

(图 1:Java 异步编程两大流派。以 JDK 8+ 为例,2026 年 8 月整理。)

二、CompletableFuture:结果没就绪,我选择不等

CompletableFuture 解决的核心问题,是「异步回调」——把提交任务和执行后的处理拆成两件事,结果好了自动回调,全程不阻塞主线程。

提交任务有两个入口:runAsync 和 supplyAsync。区别就一句话——有没有返回值。runAsync 干完就算完,返回 Void;supplyAsync 能返回一个结果,方便后面挂回调。下面这段是 runAsync 的用法(模拟发一条通知):

// runAsync:无返回值,跑完就行(模拟发一条通知)
CompletableFuture<Void> notify = CompletableFuture.runAsync(() -> {
   
    try {
   
        TimeUnit.SECONDS.sleep(2);            // 模拟耗时操作
    } catch (InterruptedException e) {
   
        Thread.currentThread().interrupt();
    }
    System.out.println(Thread.currentThread().getName() + " 通知已发送");
});
System.out.println("主线程先去干别的…");
notify.get();  // 阻塞拿结果

想要「真不阻塞 + 有返回值」,就看 supplyAsync 配合两个回调方法:whenComplete 无论成功失败都会回调,参数 t 是结果、u 是异常;exceptionally 在出错时给一个兜底值。

CompletableFuture<Integer> order = CompletableFuture.supplyAsync(() -> {
   
    System.out.println(Thread.currentThread().getName() + " 计算订单金额");
    return 200;                              // 模拟算出金额
});

Integer amount = order
    .whenComplete((val, err) -> {
               // 成功失败都会回调
        System.out.println("回调收到值 => " + val);
        System.out.println("回调收到异常 => " + err);
    })
    .exceptionally(e -> {
                       // 异常时给兜底值
        System.out.println("订单出错:" + e.getMessage());
        return -1;
    })
    .get();
System.out.println("最终金额:" + amount);

(代码适用于 JDK 8+,2026 年 8 月本地实测通过。)

第一次跑这段代码,我盯着控制台愣了半天:whenComplete 里明明没打印异常,get() 也没抛,怎么最后拿到的是 -1?后来才明白,exceptionally 返回的兜底值要走 get() 才能取到,异常在回调这一层就被「消化」掉了。这个坑,我替你踩过了。

图注建议:
来源:用户自生图(即梦/豆包/Midjourney)后替换为 PNG

下单、干别的、收通知——三步走完异步回调

可能有人会问:runAsync 和 supplyAsync 到底选哪个?

一句话:要返回值、想挂回调,用 supplyAsync;只是「跑个后台任务、不关心结果」,用 runAsync 就够了,省得多余包装。

三、ForkJoin:任务太大,就拆开并行算

如果说 CompletableFuture 在时间上做文章,ForkJoin 就是在空间上做文章。它的主场很明确:大数据量 + 计算密集——单线程算不动,就用分治拆成小块并行算,最后合并。

分治的核心就三步:拆(fork)→ 并行算 → 合(join)。以「计算 1 到 1 亿的累加」为例,任务会像这样一层层裂开,裂到足够小就直接算,结果再逐层并回去:

Java 异步编程 ForkJoin 分治求和结构:1 到 1 亿逐层拆成左右两半,叶子节点直接累加,再逐层 join 合并出最终结果

(图 2:ForkJoin 分治求和示意。)

大任务 → 拆开并行 → 汇总结果,分治三步走

ForkJoin 能高效跑起来,靠的是一个机制:工作窃取(Work Stealing)。

工作窃取(Work Stealing):ForkJoinPool 的调度机制——每个线程维护自己的双端队列,任务从队头取;线程空闲时,从别的线程队列的队尾偷任务来执行。你可以理解为「同事干完自己的活,跑来帮你分担」。

我第一次看到「窃取」两个字,心里嘀咕:这不是明抢吗?后来才懂,双端队列队尾存的是还没被处理的任务,从队尾偷,不影响另一个线程从队头干活——两个线程各拿各的,谁也不堵谁。

干完的线程不空转,去队尾帮邻居

要自己实现一个 ForkJoin 任务,得继承 RecursiveTask(有返回值)或 RecursiveAction(无返回值),重写 compute() 定义「怎么拆、怎么合」:

RecursiveTask:ForkJoin 框架里「有返回值的递归任务」抽象类,重写 compute() 写拆分与合并逻辑。你可以理解为「一张带薪分活的工单」。

任务类长这样(注意右半区间从 mid + 1 开始,否则中间值会被算两遍):

public class SumTask extends RecursiveTask<Long> {
   
    private final long start, end;
    private static final long THRESHOLD = 10_000L;
    public SumTask(long start, long end) {
   
        this.start = start; this.end = end;
    }
    @Override
    protected Long compute() {
   
        if (end - start <= THRESHOLD) {
   
            long sum = 0;
            for (long i = start; i <= end; i++) sum += i;
            return sum;
        }
        long mid = (start + end) / 2;
        SumTask left = new SumTask(start, mid);
        SumTask right = new SumTask(mid + 1, end);
        left.fork(); right.fork();
        return left.join() + right.join();
    }
}

再补一个入口跑起来(记得 import java.util.concurrent.*;):

import java.util.concurrent.ForkJoinPool;

long result = ForkJoinPool.commonPool()
        .invoke(new SumTask(1L, 100_000_000L));   // 1 到 1 亿
System.out.println("1+2+...+100000000 = " + result);

验证一下:1 到 1 亿的累加,套公式 n*(n+1)/2 算出来正好是 5000000050000000,分治没算错。

两个细节值得注意:

  1. 拆分时右区间从 mid + 1 开始——我第一次抄笔记里的写法 (start, mid) 和 (mid, end),对账怎么都对不上,就是中间值被加了两次。
  2. fork() 只是把任务压进队列,真正出结果靠 join() 阻塞等;拆太碎反而慢,阈值 THRESHOLD 要按数据量调,别照搬默认值。

四、一张对比表收尾:Java 异步编程该选哪个

两条主线各自服务的场景完全不同,别混着用。

对比项 CompletableFuture ForkJoin
核心解决的问题 结果没就绪时怎么「不等」 任务太大怎么「拆着算」
适合场景 IO 密集、异步回调、任务编排 CPU 密集、大批量计算
关键机制 回调(whenComplete / exceptionally) 分治 + 工作窃取
有无返回值 都可以,回调链处理 递归子任务,join 合并
典型入口 supplyAsync / runAsync ForkJoinPool + RecursiveTask

可能有人会问:这俩能混着用吗?

能,而且经常一起出现——比如 ForkJoin 算完一批数据,每个结果再用 CompletableFuture 异步落库。但各自的「主场」别搞反:小任务、IO 等待多,用 CompletableFuture;计算量巨大、能拆成独立子问题,才轮到 ForkJoin。

我的态度很明确:日常业务里,异步回调用得远比 fork/join 多;ForkJoin 是为大数据量计算的场景准备的,别为了炫技去拆一个几万次的循环——拆任务、偷任务都有开销,数据量小的时候,一个普通 for 循环反而是最快的。

一句话总结:CompletableFuture 管「不等」,ForkJoin 管「拆算」,它俩不是竞品,是 Java 异步编程一左一右两条腿。搞清楚了,面试问起来也不虚。


我是晚安code,持续分享编程干货。觉得有用的话记得点赞收藏和关注~也欢迎在评论区聊聊:你学 CompletableFuture 和 ForkJoin 时踩过什么坑?Java 异步编程里更常用哪一个?

目录
相关文章
|
2月前
|
XML JSON 人工智能
免费 IP 查询接口推荐:含国内精准到县区方案
本文实测2026-08-11仍可用的IP归属地查询接口。推荐4个真实可用方案:万维易源(国内精准至县区+MCP支持)、ip-api(零密钥/海外友好)、ipinfo(1000次/天)、ipwho.is(字段最全)。附调用示例、精度对比与场景选型建议。
1123 0
|
22天前
|
机器学习/深度学习 缓存 人工智能
Claude Fable 5.1 发布解读:科研智能体跑分翻倍,Agent 成本降45%
Claude Fable 5.1 于 2026 年 9 月发布:科研智能体基准翻倍、缓存降价 75%,Agent 成本最高降 45%,一文讲清升级点与价格。
296 1
Claude Fable 5.1 发布解读:科研智能体跑分翻倍,Agent 成本降45%
|
2月前
|
Shell API 调度
DeepSeek Harness 一切皆插件:开源Agent框架强在哪,怎么装
DeepSeek Harness 开发者预览版 2026 年 8 月开源,口号"一切皆插件"。本文拆解插件化架构的 4 个好处,并带你跑通安装命令。
1599 3
DeepSeek Harness 一切皆插件:开源Agent框架强在哪,怎么装
|
22天前
|
缓存 前端开发 API
Qwen3.8-Max 发布解读:2.4万亿总参数、激活950亿,百万上下文与价格拆解
Qwen3.8-Max 于 2026 年 8 月发布:2.4 万亿总参数 MoE、激活约 950 亿,最大支持 100 万上下文,价格与开源计划一并拆解。
709 0
|
22天前
|
缓存 分布式计算 安全
GPT-6 Astra 实测:贵 2.5 倍、编码追平 Claude,值吗?
GPT-6 Astra 来了,API 输入 $10、输出 $50/百万 token,约 Sol 的 2.5 倍。贵在哪、强在哪、值不值升级,一篇拆完。
546 0
GPT-6 Astra 实测:贵 2.5 倍、编码追平 Claude,值吗?
|
2月前
|
人工智能 JSON Java
LangChain4j 入门:ChatModel 第一个 AI 对话实战
LangChain4j 入门必读:Java 生态的大模型框架。从依赖配置到 ChatModel 第一个对话,再讲清 SystemMessage 与多模态输入。
210 0
|
2月前
|
人工智能 测试技术 Shell
OpenCode开源AI编程助手实操:替代Claude Code对接百炼完整教程
在AI编程Agent快速普及的当下,很多开发者习惯使用闭源编程代理工具完成项目重构、bug修复、新功能开发,但闭源工具存在诸多现实痛点。一方面工具完全绑定自家模型,无法自由切换推理后端;另一方面账号风控策略严苛,容易出现账号受限、调用中断的情况,企业内部开发还会面临代码数据外送带来的数据安全风险。OpenCode作为一款开源AI编程代理框架,被很多开发者视作Claude Code的优质替代方案,它不绑定任何大模型厂商,支持对接云端大模型服务,也可以接入本地私有化部署模型,同时完整复刻终端Agent的文件读写、命令执行、项目分析等核心能力,搭配百炼平台的各类代码大模型,就可以搭建一套完全自主可控
298 1
|
2月前
|
人工智能 JavaScript 开发工具
MCP Server 开发入门:手把手写一个能跑的 Server,三种协议怎么选
MCP Server 开发入门:用 uv 建工程装 SDK,FastMCP 写出 tool 和 resource 并跑通,再讲清三种传输协议怎么选。
169 0
|
2月前
|
运维 Java 调度
XXL-JOB 分布式定时任务框架:任务分片、失败重试、调度中心一次讲透
单机 @Scheduled 扛不住分布式定时任务?拆解 XXL-JOB 调度中心与执行器架构、任务分片、失败重试机制,附 30 分钟接入示例。
355 0
XXL-JOB 分布式定时任务框架:任务分片、失败重试、调度中心一次讲透
|
3月前
|
自然语言处理 关系型数据库 MySQL
Elasticsearch 入门教程:核心原理、Docker 部署与中文搜索实战
适合想快速上手 Elasticsearch 的后端开发者和搜索服务初学者。读完你会理解 ES 为什么搜得快、怎么在本地搭一套完整环境、怎么写查询、以及怎么把 MySQL 数据同步到 ES。
615 2
Elasticsearch 入门教程:核心原理、Docker 部署与中文搜索实战

热门文章

最新文章