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

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

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

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

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

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

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

提交任务有两个入口:runAsyncsupplyAsync。区别就一句话——有没有返回值runAsync 干完就算完,返回 VoidsupplyAsync 能返回一个结果,方便后面挂回调。下面这段是 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 异步编程里更常用哪一个?

目录
相关文章
|
25天前
|
XML JSON 人工智能
免费 IP 查询接口推荐:含国内精准到县区方案
本文实测2026-08-11仍可用的IP归属地查询接口。推荐4个真实可用方案:万维易源(国内精准至县区+MCP支持)、ip-api(零密钥/海外友好)、ipinfo(1000次/天)、ipwho.is(字段最全)。附调用示例、精度对比与场景选型建议。
575 0
|
21天前
|
缓存 前端开发 测试技术
通义千问Qwen3.7 Plus与Max实测对比:多模态能力、推理表现与性价比深度解析
在大模型应用落地的过程中,很多开发者会陷入选型困境,同样属于Qwen3.7系列的两款主力基座Qwen3.7‑Max与Qwen3.7‑Plus,都具备百万级超长上下文窗口,支持长周期Agent智能体运行,但二者在模态支持、推理侧重、计费成本、实际业务表现上存在明显分化。不少开发者只看到参数规格相近,直接盲目选用高价Max,造成业务调用成本成倍上涨;也有部分业务场景对纯文本硬核推理要求极高,选用Plus之后遇到复杂逻辑任务出现能力瓶颈。本文将从底层架构、多模态能力、基准实测数据、代码Agent表现、计费性价比、真实业务场景、API实操调用、选型避坑多个维度,完整拆解两款模型的差异,帮助个人开发者、
223 1
|
25天前
|
人工智能 缓存 API
‌DeepSeek V4 Pro在阿里云怎么调用?阿里云百炼支持‌DeepSeek V4 Pro吗?
阿里云百炼正式支持DeepSeek V4 Pro(模型ID:deepseek-v4-pro),支持OpenAI/DashScope/Anthropic兼容调用,享免费100万Tokens。支持思考模式、Function Calling及百万级上下文,可按量付费或订阅TokenPlan(个人版39元/月)。在阿里云百炼官网:https://t.aliyun.com/U/fPVHqY 免费领取千万Tokens
|
25天前
|
缓存 人工智能 JSON
DeepSeek V4 Pro转正,性能逼近Fable 5,价格仅六十分之一
DeepSeek V4 Pro正式版发布:1M上下文、384K输出,价格只有Fable 5的六十分之一。本文从价格、能力、跑分可信度拆解,说清该不该切。
280 0
|
1月前
|
运维 Java 调度
XXL-JOB 分布式定时任务框架:任务分片、失败重试、调度中心一次讲透
单机 @Scheduled 扛不住分布式定时任务?拆解 XXL-JOB 调度中心与执行器架构、任务分片、失败重试机制,附 30 分钟接入示例。
228 0
XXL-JOB 分布式定时任务框架:任务分片、失败重试、调度中心一次讲透
|
2月前
|
自然语言处理 关系型数据库 MySQL
Elasticsearch 入门教程:核心原理、Docker 部署与中文搜索实战
适合想快速上手 Elasticsearch 的后端开发者和搜索服务初学者。读完你会理解 ES 为什么搜得快、怎么在本地搭一套完整环境、怎么写查询、以及怎么把 MySQL 数据同步到 ES。
499 2
Elasticsearch 入门教程:核心原理、Docker 部署与中文搜索实战
|
1月前
|
机器学习/深度学习 人工智能 缓存
DeepSeek V4 Flash 对标 Gemini 3.6,AI 大跑毒时代
DeepSeek V4 Flash 0731 上线公测,智能指数追平 Gemini 3.6 Flash,价格仅其零头,拆解「大跑毒时代」谁先出局。
378 0
DeepSeek V4 Flash 对标 Gemini 3.6,AI 大跑毒时代
|
21天前
|
存储 关系型数据库 MySQL
读写混合TPS差六倍,PostgreSQL与MySQL架构差异实测
从架构设计、索引实现、事务隔离、复制机制、运维体验五个维度深度对比PostgreSQL与MySQL,覆盖MySQL 9.0向量检索与PostgreSQL 17新特性,附权威基准数据和选型决策框架
|
22天前
|
人工智能 缓存 架构师
从需求文档到测试计划再到测试报告,Opencode一条龙流水线搭建教程(附完整配置)
本文介绍如何用Opencode构建端到端测试流水线:将需求解析、测试计划、用例生成、执行分析与报告生成拆解为5条可复用指令(/spec→/plan→/testgen→/test→/report),一次配置,终身调用。AI自动串联各环节,100页PRD到完整测试报告仅需2小时,解放测试工程师于重复劳动,让经验沉淀为可复用流程。
|
3月前
|
前端开发 Java Nacos
Nacos 注解全解析:7 个核心注解 + 5 个生产踩坑清单(2026 实测)
Nacos 注解用错,是 90% 微服务 Bug 的根源。本文把注册发现、配置热更新的 7 个核心注解、动态刷新原理和 5 个生产踩坑一次讲透,附可运行代码。
495 2
Nacos 注解全解析:7 个核心注解 + 5 个生产踩坑清单(2026 实测)

热门文章

最新文章