跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

CompletableFuture 异步编程完全指南:从基础到实战

介绍 Java CompletableFuture 异步编程核心用法。内容涵盖任务创建(runAsync/supplyAsync)、结果链式处理(thenApply/thenAccept/thenRun)、多任务组合(thenCompose/thenCombine/allOf/anyOf)及异常处理策略。重点强调使用自定义线程池避免资源耗尽,并对比 get() 与 join() 的区别,提供实战中的最佳实践建议。

草莓泡芙发布于 2026/3/22更新于 2026/7/2465 浏览
CompletableFuture 异步编程完全指南:从基础到实战

CompletableFuture 异步编程完全指南:从基础到实战

在 Java 异步编程领域,CompletableFuture 绝对是'瑞士军刀'级别的工具——它解决了传统 Future 无法链式调用、缺乏异常处理、难以组合多任务的痛点,让异步代码变得简洁、可读且易维护。无论是处理单个异步任务的结果,还是协调多个任务的依赖关系,CompletableFuture 都能轻松应对。本文将从基础用法到实战技巧,帮你彻底掌握 CompletableFuture 的核心能力。

一、基础:如何创建异步任务?

CompletableFuture 的入门操作是创建异步任务,分为无返回值和有返回值两种场景,对应两个核心方法:runAsync 和 supplyAsync。

1. 无返回值任务:runAsync

适合只需要执行操作、不需要返回结果的场景(比如日志记录、文件备份),底层基于 Runnable 接口实现。

代码示例:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CompletableFutureDemo {
    public static void main(String[] args) throws InterruptedException {
        // 1. 默认线程池(不推荐):使用 ForkJoinPool.commonPool(),易耗尽资源
        CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
            System.out.println("无返回值任务 1,线程:" + Thread.currentThread().getName());
            Thread.sleep(1000); // 模拟耗时操作
        });

        // 2. 自定义线程池(推荐):控制线程数量,避免资源耗尽
        ExecutorService executor = Executors.newFixedThreadPool(2);
        CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
            System.out.println("无返回值任务 2,线程:" + Thread.currentThread().getName());
            Thread.sleep(1000);
        }, executor);

        // 主线程等待任务完成(否则 JVM 提前退出)
        Thread.sleep(2000);
        executor.shutdown(); // 关闭自定义线程池
    }
}
关键提醒:
  • !默认线程池是 JVM 共享的 ForkJoinPool.commonPool(),若大量任务同时执行,容易耗尽线程资源导致系统崩溃。
务必使用自定义线程池
2. 有返回值任务:supplyAsync

适合需要异步执行并返回结果的场景(比如调用远程接口、查询数据库),底层基于 Supplier 接口实现,是最常用的创建方式。

代码示例:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class CompletableFutureDemo {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        // 创建有返回值的异步任务
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("有返回值任务,线程:" + Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(1); // 模拟耗时 1 秒
            return "异步任务执行结果"; // 返回结果
        }, executor);

        // 阻塞获取结果(后续会讲更优雅的链式处理)
        String result = future.get();
        System.out.println("获取到的结果:" + result);
        executor.shutdown();
    }
}
输出结果:
有返回值任务,线程:pool-1-thread-1 获取到的结果:异步任务执行结果 

二、核心:任务完成后的链式结果处理

CompletableFuture 的灵魂优势是链式调用——任务完成后,无需阻塞等待结果,直接通过回调方法处理。常用的三个方法可以用'输入输出规则'快速区分:

方法名输入(是否接收任务结果)输出(是否返回新结果)适用场景
thenApply是是加工结果并返回新值(比如字符串拼接)
thenAccept是否消费结果(比如打印日志)
thenRun否否任务完成后做收尾(比如发送通知)
代码示例:链式处理流程
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;

public class CompletableFutureDemo {
    public static void main(String[] args) throws Exception {
        // 1. 创建有返回值的初始任务
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "原始结果", Executors.newSingleThreadExecutor());

        // 2. thenApply:加工结果(原始结果 -> 加工后结果)
        CompletableFuture<String> applyFuture = future.thenApply(result -> result + " -> 经过 thenApply 处理");

        // 3. thenAccept:消费结果(打印加工后的结果)
        CompletableFuture<Void> acceptFuture = applyFuture.thenAccept(finalResult -> System.out.println("thenAccept 消费结果:" + finalResult));

        // 4. thenRun:任务完成后收尾(打印完成提示)
        CompletableFuture<Void> runFuture = acceptFuture.thenRun(() -> System.out.println("所有任务完成,执行 thenRun"));

        // 等待最终任务完成
        runFuture.get();
    }
}
输出结果:
thenAccept 消费结果:原始结果 -> 经过 thenApply 处理 所有任务完成,执行 thenRun 

三、进阶:多任务组合的实战技巧

实际开发中,异步任务往往不是孤立的——要么串行依赖(一个任务的输入需要另一个任务的输出),要么并行执行(多个任务同时进行,最后合并结果),要么批量处理(等待所有任务完成或任意一个完成)。CompletableFuture 提供了针对性的解决方案。

1. 串行依赖:thenCompose(避免嵌套地狱)

当任务 B 的输入依赖任务 A 的输出时,用 thenCompose 代替嵌套的 CompletableFuture,让代码更扁平。

场景示例:

模拟'根据用户 ID 查用户名→根据用户名查用户信息'的串行流程。

代码示例:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;

public class CompletableFutureDemo {
    // 模拟根据用户 ID 获取用户名(返回 CompletableFuture)
    private static CompletableFuture<String> getUsernameById(Integer id) {
        return CompletableFuture.supplyAsync(() -> {
            System.out.println("获取用户名,线程:" + Thread.currentThread().getName());
            return "用户" + id; // 比如用户 1→"用户 1"
        }, Executors.newSingleThreadExecutor());
    }

    // 模拟根据用户名获取用户信息(返回 CompletableFuture)
    private static CompletableFuture<String> getUserInfoByName(String name) {
        return CompletableFuture.supplyAsync(() -> {
            System.out.println("获取用户信息,线程:" + Thread.currentThread().getName());
            return name + " - 年龄 20"; // 比如"用户 1"→"用户 1 - 年龄 20"
        }, Executors.newSingleThreadExecutor());
    }

    public static void main(String[] args) throws Exception {
        // 串行调用:getUsernameById → getUserInfoByName
        CompletableFuture<String> finalFuture = getUsernameById(1).thenCompose(name -> getUserInfoByName(name));
        // 关键:thenCompose 扁平化调用
        System.out.println("最终结果:" + finalFuture.get());
    }
}
输出结果:
获取用户名,线程:pool-1-thread-1 获取用户信息,线程:pool-2-thread-1 最终结果:用户 1 - 年龄 20 
2. 并行组合:thenCombine(合并两个任务的结果)

当两个任务并行执行,都完成后需要合并结果时,用 thenCombine(比如查商品价格和折扣,计算最终价格)。

场景示例:

模拟'查商品价格→查折扣→计算最终价格'的并行流程。

代码示例:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;

public class CompletableFutureDemo {
    // 模拟获取商品价格
    private static CompletableFuture<Double> getPrice(String product) {
        return CompletableFuture.supplyAsync(() -> {
            System.out.println("获取" + product + "价格,线程:" + Thread.currentThread().getName());
            return 100.0; // 商品价格 100 元
        }, Executors.newSingleThreadExecutor());
    }

    // 模拟获取商品折扣
    private static CompletableFuture<Double> getDiscount() {
        return CompletableFuture.supplyAsync(() -> {
            System.out.println("获取折扣,线程:" + Thread.currentThread().getName());
            return 0.8; // 8 折
        }, Executors.newSingleThreadExecutor());
    }

    public static void main(String[] args) throws Exception {
        // 并行执行两个任务,合并结果(价格 * 折扣)
        CompletableFuture<Double> finalPriceFuture = getPrice("手机").thenCombine(getDiscount(), (price, discount) -> price * discount);
        System.out.println("最终价格:" + finalPriceFuture.get());
    }
}
输出结果:
获取手机价格,线程:pool-1-thread-1 获取折扣,线程:pool-2-thread-1 最终价格:80.0 
3. 批量任务:allOf / anyOf(等待所有或任意任务完成)
  • allOf:等待所有异步任务完成(适合批量操作,比如同步多个数据源后统一处理);
  • anyOf:等待任意一个异步任务完成(适合'最快响应'场景,比如调用多个镜像接口取最快的结果)。
代码示例:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;

public class CompletableFutureDemo {
    // 模拟一个延迟完成的任务(延迟时间=num*1 秒)
    private static CompletableFuture<String> task(Integer num) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(num * 1000);
            } catch (InterruptedException e) {
            }
            System.out.println("任务" + num + "完成");
            return "结果" + num;
        }, Executors.newFixedThreadPool(3)); // 固定 3 个线程处理批量任务
    }

    public static void main(String[] args) throws Exception {
        // 1. allOf:等待所有任务完成(任务 1、2、3 都完成后打印提示)
        CompletableFuture<Void> allFuture = CompletableFuture.allOf(task(1), task(2), task(3));
        allFuture.get(); // 阻塞等待所有任务完成
        System.out.println("===== 所有任务都完成了 =====");

        // 2. anyOf:等待任意一个任务完成(取第一个完成的任务结果)
        CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(task(1), task(2), task(3));
        System.out.println("第一个完成的任务结果:" + anyFuture.get());
    }
}
输出结果:
任务 1 完成 任务 2 完成 任务 3 完成 ===== 所有任务都完成了 ===== 任务 1 完成 第一个完成的任务结果:结果 1 

四、关键:别让异常悄悄溜走——异步任务的异常处理

异步任务的异常很'隐蔽'——如果不主动处理,它不会像同步代码那样直接抛出,而是悄悄消失。CompletableFuture 提供了三种核心异常处理方式,覆盖不同场景:

1. exceptionally:异常时返回兜底值

当任务抛出异常时,返回一个默认值(比如接口调用失败时返回空列表)。

示例场景:

模拟'任务执行时抛出除以零异常',用 exceptionally 返回兜底值。

2. handle:处理所有情况(正常/异常)并返回结果

无论任务是正常完成还是抛出异常,都能统一处理并返回新结果(比如正常时返回结果,异常时返回错误信息)。

3. whenComplete:消费结果或异常(无返回值)

无论任务是否异常,都能消费结果或异常信息(比如打印日志),但不返回新结果。

综合代码示例:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;

public class CompletableFutureDemo {
    public static void main(String[] args) throws Exception {
        // 模拟一个会抛出异常的异步任务(1/0)
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("执行异步任务");
            int a = 1 / 0; // 模拟异常
            return "正常结果";
        }, Executors.newSingleThreadExecutor());

        // 1. exceptionally:异常时返回兜底值("异常兜底值")
        CompletableFuture<String> exceptionFuture = future.exceptionally(e -> {
            System.out.println("捕获异常:" + e.getMessage());
            return "异常兜底值";
        });

        // 2. handle:处理所有情况(正常/异常)并返回结果
        CompletableFuture<String> handleFuture = exceptionFuture.handle((result, e) -> {
            if (e != null) {
                return "handle 处理异常:" + e.getMessage(); // 异常时返回错误信息
            } else {
                return "handle 处理正常:" + result; // 正常时返回结果
            }
        });

        // 3. whenComplete:消费最终结果(打印结果)
        CompletableFuture<Void> whenCompleteFuture = handleFuture.whenComplete((result, e) -> {
            System.out.println("whenComplete 最终结果:" + result);
        });
        whenCompleteFuture.get();
    }
}
输出结果:
执行异步任务 捕获异常:/ by zero whenComplete 最终结果:handle 处理异常:/ by zero 

五、细节:get() vs join()——该用哪个获取结果?

获取 CompletableFuture 的结果有两种常用方式,核心区别是异常处理:

方法名异常类型适用场景
get()抛出检查异常(需要 try-catch 或 throws 声明)同步代码中需要明确处理异常的场景
join()抛出非检查异常(不需要 try-catch)Lambda 表达式或链式调用中,简化代码
代码示例:
import java.util.concurrent.CompletableFuture;

public class CompletableFutureDemo {
    public static void main(String[] args) {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "结果");

        // 1. get():需要捕获异常
        try {
            String result1 = future.get();
            System.out.println("get 获取结果:" + result1);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 2. join():无需捕获异常(更适合 Lambda)
        String result2 = future.join();
        System.out.println("join 获取结果:" + result2);
    }
}
输出结果:
get 获取结果:结果 join 获取结果:结果 

六、总结:CompletableFuture 核心用法速查表

通过本文的讲解,CompletableFuture 的常用能力可以归纳为 5 个核心点,直接对照使用即可:

  1. 创建任务:runAsync(无返回值)、supplyAsync(有返回值),必用自定义线程池;
  2. 结果处理:thenApply(加工结果)、thenAccept(消费结果)、thenRun(收尾操作),实现链式调用;
  3. 多任务组合:thenCompose(串行依赖)、thenCombine(并行合并)、allOf/anyOf(批量任务);
  4. 异常处理:exceptionally(兜底返回)、handle(处理并返回)、whenComplete(消费结果/异常);
  5. 结果获取:join()(非检查异常)比 get()(检查异常)更适合 Lambda 表达式。

最后:写在实战前的建议

  • 优先使用自定义线程池:避免默认线程池耗尽,建议根据业务场景选择 FixedThreadPool(固定大小)或 CachedThreadPool(缓存线程);
  • 避免过度链式调用:链式调用虽好,但过长的链会降低可读性,建议拆分逻辑为多个方法;
  • 必处理异常:异步任务的异常不会主动抛出,务必用 exceptionally/handle/whenComplete 兜底。

掌握 CompletableFuture,不是为了'炫技',而是为了写出更高效、可维护的代码。

目录

  1. CompletableFuture 异步编程完全指南:从基础到实战
  2. 一、基础:如何创建异步任务?
  3. 1. 无返回值任务:runAsync
  4. 代码示例:
  5. 关键提醒:
  6. 2. 有返回值任务:supplyAsync
  7. 代码示例:
  8. 输出结果:
  9. 二、核心:任务完成后的链式结果处理
  10. 代码示例:链式处理流程
  11. 输出结果:
  12. 三、进阶:多任务组合的实战技巧
  13. 1. 串行依赖:thenCompose(避免嵌套地狱)
  14. 场景示例:
  15. 代码示例:
  16. 输出结果:
  17. 2. 并行组合:thenCombine(合并两个任务的结果)
  18. 场景示例:
  19. 代码示例:
  20. 输出结果:
  21. 3. 批量任务:allOf / anyOf(等待所有或任意任务完成)
  22. 代码示例:
  23. 输出结果:
  24. 四、关键:别让异常悄悄溜走——异步任务的异常处理
  25. 1. exceptionally:异常时返回兜底值
  26. 示例场景:
  27. 2. handle:处理所有情况(正常/异常)并返回结果
  28. 3. whenComplete:消费结果或异常(无返回值)
  29. 综合代码示例:
  30. 输出结果:
  31. 五、细节:get() vs join()——该用哪个获取结果?
  32. 代码示例:
  33. 输出结果:
  34. 六、总结:CompletableFuture 核心用法速查表
  35. 最后:写在实战前的建议
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 玄武 CLI:国产芯片本地大模型部署工具详解
  • 6 款主流 AI 检测降重工具实测对比与选择建议
  • Spring Cloud 集成 Dubbo 微服务调用实践
  • OpenClaw Skills 合集开源:700+ 本地 AI Agent 技能插件盘点
  • RISC-V 开源处理器实战:从 Verilog RTL 设计到 FPGA 原型验证
  • Atlas 数据中间件架构与核心特性介绍
  • VS Code 前端开发高效插件推荐与配置实战
  • Python 实现手机号归属地查询工具
  • 深度拆解美团闪购 AIGC 营销案例:技术如何赋能品牌叙事
  • 微信域名验证失败:使用 Nginx 配置返回验证文件内容
  • AIGC Bar API 平台接入与工程化实践指南
  • C++ 模板进阶:非类型参数、特化与分离编译详解
  • Python 数据统计实战指南:从环境配置到高级分析
  • Dify 工作流集成 TTS:低代码实现语音输出
  • React Native 集成开源鸿蒙应用:WebView 桥接与原生模块方案
  • LLaMaFactory 基于魔搭社区免费 GPU 微调大模型实战
  • 小爱音乐 Docker 部署手册
  • 通义千问插件在 IDEA 中的 Java 开发实战应用
  • Stable Diffusion 本地模型 base_model 路径配置与加载指南
  • WebView 技术详解:原理、场景与性能安全挑战

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online