CompletableFuture 完整详解 + 全场景使用案例

一、基础介绍

1. 定位

CompletableFuture 是 Java 8 引入的类,同时实现两个接口:

  • Future:原有异步任务结果获取接口(阻塞get()
  • CompletionStage链式异步编排接口 ,支持任务串行、并行、异常处理、回调,解决传统 FutureTask 只能阻塞获取结果的痛点。

2. 核心优势对比 FutureTask

  1. 支持非阻塞回调 :不用阻塞get(),任务完成自动执行回调;
  2. 链式编排thenApply/thenCombine/thenRun 串联、并行多个异步任务;
  3. 统一异常处理exceptionally() / handle() 捕获异步任务异常;
  4. 批量等待allOf() 等待全部任务、anyOf() 任意一个完成就返回;
  5. 手动完成任务complete() 手动设置结果、completeExceptionally() 手动抛异常。

3. 两种运行模式

(1)默认线程池:ForkJoinPool.commonPool()

不带自定义线程池的方法(supplyAsync()/runAsync())默认使用公共池,IO密集型任务不推荐(会占满公共池阻塞其他任务)。

(2)自定义线程池重载

所有异步方法都提供第二个参数 Executor,生产环境必须传入自定义线程池隔离业务。

二、核心API分类

1. 创建异步任务(2种)

方法 作用 返回值
runAsync(Runnable) 无入参、无返回值异步任务 CompletableFuture<Void>
supplyAsync(Supplier<T>) 无入参、有返回值异步任务 CompletableFuture<T>

2. 串行执行(上一个任务结果作为下一个入参)

方法 区别
thenApply(Function) 有返回值,同线程执行
thenApplyAsync(Function) 有返回值,线程池异步执行
thenConsume(Consumer) 无返回值,消费上一步结果
thenRun(Runnable) 不接收上一步结果,无返回值

3. 并行组合多个任务

  1. thenCombine:两个任务都完成,合并两个结果计算新值
  2. thenAcceptBoth:两个任务完成,消费两个结果,无返回
  3. runAfterBoth:两个任务完成,执行无参无返回任务
  4. applyToEither:两个任务任意一个先完成,使用最快的结果
  5. acceptEither:任意一个完成,消费最快结果

4. 批量等待多任务

  • CompletableFuture.allOf(cf1,cf2,cf3)全部任务完成才结束,无返回值
  • CompletableFuture.anyOf(cf1,cf2,cf3)任意一个任务完成立刻返回,返回最先完成的结果

5. 异常处理(三种方式)

  1. exceptionally():任务异常时提供兜底返回值,只能接收异常
  2. handle():无论正常/异常都会执行,同时拿到结果+异常(推荐)
  3. whenComplete():回调,无返回值,仅做日志、监控

6. 手动控制任务完成

  • complete(T result):手动正常完成,设置返回值
  • completeExceptionally(Throwable ex):手动抛出异常
  • cancel(boolean):取消任务

三、完整使用案例(分场景)

前置:自定义业务线程池(生产标准)

java 复制代码
import java.util.concurrent.*;

public class CompletableFutureDemo {
    // 业务专用线程池,禁止使用公共ForkJoin池
    private static final Executor BIZ_POOL = new ThreadPoolExecutor(
            10,
            20,
            60L,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(200),
            new ThreadPoolExecutor.CallerRunsPolicy()
    );

场景1:基础创建异步任务(有返回/无返回)

java 复制代码
    // 1. 无返回值 runAsync
    public static void testRunAsync() throws ExecutionException, InterruptedException {
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            System.out.println("无返回任务执行,线程:" + Thread.currentThread().getName());
        }, BIZ_POOL);
        future.get(); // 阻塞等待完成
    }

    // 2. 有返回值 supplyAsync
    public static void testSupplyAsync() throws ExecutionException, InterruptedException {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("执行查询任务");
            return "查询结果:Java多线程";
        }, BIZ_POOL);
        // 阻塞获取结果
        String res = future.get();
        System.out.println(res);
    }

场景2:串行链式调用 thenApply(任务分步依赖)

第一步查用户ID → 第二步根据ID查用户名

java 复制代码
    public static void testThenApply() throws ExecutionException, InterruptedException {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            // 第一步:获取用户ID
            return 1001L;
        }, BIZ_POOL).thenApplyAsync(userId -> {
            // 第二步:根据ID查名称,接收上一步结果
            return "用户" + userId + "_张三";
        }, BIZ_POOL);

        String name = future.get();
        System.out.println(name); // 用户1001_张三
    }

场景3:异常处理 handle / exceptionally

方式1:handle(推荐,正常/异常都进入回调)

java 复制代码
    public static void testHandle() throws InterruptedException {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            int a = 1 / 0; // 模拟异常
            return "正常数据";
        }, BIZ_POOL).handle((result, ex) -> {
            if (ex != null) {
                System.out.println("任务异常:" + ex.getMessage());
                return "兜底默认值";
            }
            return result;
        });
        System.out.println(future.join()); // join不抛受检异常
    }

方式2:exceptionally 仅异常进入

java 复制代码
    public static void testExceptionally() {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            int a = 1 / 0;
            return "ok";
        }, BIZ_POOL).exceptionally(ex -> {
            System.out.println("捕获异常:" + ex);
            return "异常兜底";
        });
        System.out.println(future.join());
    }

场景4:两个任务并行合并 thenCombine

任务A查商品信息、任务B查库存,两个都完成后组装返回

java 复制代码
    public static void testThenCombine() throws ExecutionException, InterruptedException {
        // 任务1:查询商品名称
        CompletableFuture<String> goodsCf = CompletableFuture.supplyAsync(() -> "Java并发编程实战", BIZ_POOL);
        // 任务2:查询库存
        CompletableFuture<Integer> stockCf = CompletableFuture.supplyAsync(() -> 99, BIZ_POOL);

        // 两个任务全部完成,合并结果
        CompletableFuture<String> resultCf = goodsCf.thenCombine(stockCf, (goods, stock) -> {
            return "商品:" + goods + ",剩余库存:" + stock;
        });

        System.out.println(resultCf.get());
    }

场景5:任意任务完成即返回 anyOf

并发调用多个接口,取最快返回的结果(如多服务冗余查询)

java 复制代码
    public static void testAnyOf() throws ExecutionException, InterruptedException {
        CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "服务A返回数据";
        }, BIZ_POOL);

        CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "服务B返回数据";
        }, BIZ_POOL);

        CompletableFuture<Object> fastResult = CompletableFuture.anyOf(cf1, cf2);
        System.out.println(fastResult.get()); // 输出服务B,执行更快
    }

场景6:全部任务完成 allOf(多线程本地事务一致性核心场景)

你之前需求:多线程执行业务SQL,全部执行完毕统一提交/回滚

java 复制代码
    public static void testAllOfTx() throws InterruptedException {
        // 模拟3个数据库任务,独立事务连接
        CompletableFuture<Void> task1 = CompletableFuture.runAsync(() -> System.out.println("执行订单表写入"), BIZ_POOL);
        CompletableFuture<Void> task2 = CompletableFuture.runAsync(() -> System.out.println("执行库存表扣减"), BIZ_POOL);
        CompletableFuture<Void> task3 = CompletableFuture.runAsync(() -> {
            int err = 1 / 0; // 模拟任务异常
        }, BIZ_POOL);

        // 收集所有任务
        CompletableFuture<Void> allTask = CompletableFuture.allOf(task1, task2, task3);

        try {
            allTask.join();
        } catch (CompletionException e) {
            // 任意任务抛出异常,全局标记失败,统一回滚所有事务
            System.err.println("存在任务执行失败,全部事务回滚,异常:" + e.getCause());
            return;
        }
        // 无异常,统一提交所有事务
        System.out.println("所有任务执行成功,统一提交事务");
    }

场景7:手动完成 complete / completeExceptionally

不依赖异步线程,手动控制任务结果,适合回调接口封装

java 复制代码
    public static void testManualComplete() {
        CompletableFuture<String> future = new CompletableFuture<>();
        // 模拟异步回调后手动赋值
        new Thread(() -> {
            try {
                Thread.sleep(500);
                future.complete("手动设置成功结果");
                // 异常场景:future.completeExceptionally(new RuntimeException("接口调用失败"));
            } catch (InterruptedException e) {
                future.completeExceptionally(e);
            }
        }).start();

        String res = future.join();
        System.out.println(res);
    }

场景8:whenComplete 后置回调(日志、监控埋点)

无论成功失败,执行收尾逻辑,不修改返回值

java 复制代码
    public static void testWhenComplete() {
        CompletableFuture.supplyAsync(() -> {
            return "业务执行完成";
        }, BIZ_POOL).whenComplete((res, ex) -> {
            // 统一打印日志、记录耗时、埋点监控
            if (ex != null) {
                System.err.println("任务异常日志:" + ex.getMessage());
            } else {
                System.out.println("任务正常结束,结果:" + res);
            }
        }).join();
    }

四、get() 和 join() 核心区别

  1. future.get()
    • 抛出受检异常InterruptedException, ExecutionException,必须try-catch捕获
  2. future.join()
    • 只抛出运行时异常 CompletionException,无需强制捕获,代码更简洁,业务开发优先用join

五、生产环境避坑要点

  1. 禁止不指定线程池
    无executor参数的supplyAsync/runAsync使用ForkJoinPool.commonPool,IO阻塞任务会占满公共池,导致全局异步任务卡死。

  2. allOf 不会自动抛出子任务异常
    必须调用join()/get()才会触发异常,否则感知不到子任务报错。

  3. 线程池资源释放
    程序关闭时手动shutdown()自定义线程池,避免线程泄漏。

  4. 异步任务中传递上下文(ThreadLocal失效)
    异步线程会丢失主线程ThreadLocal(登录用户、请求ID),需要手动拷贝上下文到异步任务。

  5. 超时控制
    长时间任务搭配 orTimeout(time, unit) 防止无限阻塞:

    java 复制代码
    cf.orTimeout(3, TimeUnit.SECONDS).handle(...)

六、总结

CompletableFuture.allOf()单机多线程事务一致性最优工具

  1. 每个任务独立数据库连接、关闭自动提交;
  2. 异步执行业务DML,异常自动封装进CompletionException;
  3. allOf().join() 等待全部任务执行完毕;
  4. 捕获异常则批量回滚所有连接,无异常统一commit;
    相比CountDownLatch/CyclicBarrier/Phaser代码更简洁,天然支持异步编排,无需手动维护计数器。
相关推荐
许心月2 小时前
Java学习资料网站汇总
java
c238562 小时前
第二篇:《测试指挥官:可视化单题自测框架(含 assert 实操)》
java·数据库·c++·算法·安全性测试
帅次3 小时前
Kotlin 与 Java 互操作:混合工程里的平台类型与 API 边界
java·开发语言·kotlin·suspend·nullable
X-⃢_⃢-X3 小时前
一、第一阶段:认识 Spring Boot
java·spring boot·后端
前端工作日常3 小时前
我学习到的Java程序的生命周期
java·后端
SunnyDays10114 小时前
Java 实现 PDF 页面删除、排序、旋转和裁剪
java·pdf
人工干智能4 小时前
科普:Pandas 索引器 loc 与 iloc 的编程思维
java·人工智能·pandas
持力行5 小时前
C++与Java变量声明、定义及内存分配的核心区别
java·开发语言·c++
接着奏乐接着舞7 小时前
大模型开发 agent 知识库
java