Java CompletableFuture 核心API

前言

随着异步编程在现代高并发系统中的地位日益提升,Java 8 引入的 CompletableFuture 成为处理异步任务编排的重要工具。它不仅弥补了传统 Future 在回调机制和组合能力上的不足,还通过函数式风格 API 极大地提升了代码的可读性与可维护性。本文介绍了 CompletableFuture 中关于异步任务编排、异常处理与超时控制等实用的核心 API。

1. CompletableFuture背景概述

CompletableFuture 自 Java 8 引入,是 java.util.concurrent 包下对 Future 接口的增强实现,同时实现了 CompletionStage 接口。在Java 8之前,一般通过Future实现异步。

  • Future用于表示异步计算的结果,只能通过阻塞或轮询的方式获取结果,而且不支持设置回调方法,若要设置回调一般使用guava的ListenableFuture,又引入了回调地狱问题。
  • CompletableFutureFuture进行了扩展,提供了更丰富的API用于:
    • 支持回调处理计算结果
    • 支持组合操作和异步编排
    • 解决回调地狱问题

2. 核心概念

2.1 CompletionStage 接口

CompletableFuture 实现了2个接口:FutureCompletionStageFuture表示异步计算的结果,CompletionStage表示异步执行的一个步骤(Stage),这个步骤可能是由另外一个CompletionStage触发的,对着当前步骤的完成,也可能触发其他一系列CompletionStage的执行。从而我们可以根据实际业务对这些步骤进行编排组合,CompletionStage接口正式定义了这样的能力,我们可以通过其提供的thenApplythenCompose等函数式编程方法来组合编排这些步骤。

2.2 执行与完成

  • 异步执行 :通过 supplyAsyncrunAsync 方法提交任务至线程池执行,并在任务完成后自动标记完成。
  • 显式完成 :在任何线程中,可通过 complete(T value)completeExceptionally(Throwable ex) 方法手动设置结果或异常,提前终止计算流程。此机制是实现超时、熔断等功能的基础。

3. 核心 API 详解

3.0 调试通用工具类

java 复制代码
public class CommonUtils {
    private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:sss");
    
    public static void printLog(String log) {
        StringJoiner joiner = new StringJoiner(" - ")
                .add(getCurrentTime()) // 返回当前时间:yyyy-MM-dd HH:mm:ss:sss
                .add("【" + Thread.currentThread().getName()) // 返回线程名
                .add(String.format("%2d 】", Thread.currentThread().getId())) // 返回线程ID
                .add(log); // 日志内容

        System.out.println(joiner);
    }

    public static void secondSleep(long seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public static String getCurrentTime() {
        long time = System.currentTimeMillis();
        return FORMAT.format(time);
    }
}

3.1 创建实例

此类方法返回一个新的 CompletableFuture对象,可以调用join()等方法获取返回值

java 复制代码
private static void createCF() {
    // 执行有返回值的异步任务,使用默认 ForkJoinPool
    CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task1 Result");
    CommonUtils.printLog(future1.join());
    // 执行有返回值的异步任务,使用自定义线程池
    ExecutorService threadPool = Executors.newFixedThreadPool(3);
    CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task2 Result", threadPool);
    CommonUtils.printLog(future2.join());
    // 执行无返回值的异步任务
    CompletableFuture<Void> future3 = CompletableFuture.runAsync(() -> CommonUtils.printLog("Task3 done"));

    // 执行无返回值的异步任务,使用自定义线程池
    CompletableFuture<Void> future4 = CompletableFuture.runAsync(() -> CommonUtils.printLog("Task4 done"), threadPool);

    // 创建一个已完成的Future
    CompletableFuture<String> completedFuture = CompletableFuture.completedFuture("instant result");
    CommonUtils.printLog(completedFuture.join());
}

上述方法的执行结果如下,从结果中可以看到future3和future4所用的线程池不同。

console 复制代码
2025-08-19 20:47:58:058 - 【main -  1 】 - Task1 Result
2025-08-19 20:47:58:058 - 【main -  1 】 - Task2 Result
2025-08-19 20:47:58:058 - 【ForkJoinPool.commonPool-worker-1 - 24 】 - Task3 done
2025-08-19 20:47:58:058 - 【main -  1 】 - instant result
2025-08-19 20:47:58:058 - 【pool-1-thread-2 - 26 】 - Task4 done

3.2 结果转换与消费

此类方法返回一个新的 CompletionStage,用于承接上一阶段的结果,可以分为3个系列:

  • thenApply(Function<? super T,? extends U> fn) 系列对上一阶段的结果进行同步转换,
  • thenAccept(Consumer<? super T> action) 系列用于同步消费结果,无返回值,
  • thenRun(Runnable action) 系列用于上一阶段完成后同步执行一个动作,不关心其结果

以上方法均提供 Async 版本(如 thenAcceptAsync),用于指定回调函数在另一个线程(默认或自定义线程池)中异步执行,用于控制线程执行边界,且所有 Async 方法都有重载版本,允许指定自定义的线程池。

下面以买咖啡的例子来展示各个系列方法的使用:

java 复制代码
private static void buyCoffee() {
    ExecutorService threadPool = Executors.newFixedThreadPool(3);

    // 1. 店员制作并打包咖啡
    CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
        CommonUtils.printLog("开始制作 生椰丝绒拿铁...");
        CommonUtils.secondSleep(1);
        CommonUtils.printLog("完成制作 生椰丝绒拿铁...");
        return "生椰丝绒拿铁";
    }, threadPool).thenApply(coffee -> {
        CommonUtils.printLog("开始打包 生椰丝绒拿铁...");
        CommonUtils.secondSleep(1);
        CommonUtils.printLog("完成打包 生椰丝绒拿铁...");
        return String.format("打包好的【%s】", coffee);
    });

    CommonUtils.printLog(future1.join());

    // 2. 顾客取走咖啡
    CompletableFuture<Void> future2 = future1
                .thenAcceptAsync(coffee -> CommonUtils.printLog("取走" + coffee), threadPool);

    // 3. 顾客取走咖啡后,店员对顾客说:欢迎下次光临
    future2.thenRunAsync(() -> CommonUtils.printLog("欢迎下次光临"), threadPool);
}

输出结果:

console 复制代码
2025-08-19 21:14:21:021 - 【pool-1-thread-1 - 24 】 - 开始制作 生椰丝绒拿铁...
2025-08-19 21:14:22:022 - 【pool-1-thread-1 - 24 】 - 完成制作 生椰丝绒拿铁...
2025-08-19 21:14:22:022 - 【pool-1-thread-1 - 24 】 - 开始打包 生椰丝绒拿铁...
2025-08-19 21:14:23:023 - 【pool-1-thread-1 - 24 】 - 完成打包 生椰丝绒拿铁...
2025-08-19 21:14:23:023 - 【main -  1 】 - 打包好的【生椰丝绒拿铁】
2025-08-19 21:14:23:023 - 【pool-1-thread-2 - 25 】 - 取走打包好的【生椰丝绒拿铁】
2025-08-19 21:14:23:023 - 【pool-1-thread-3 - 26 】 - 欢迎下次光临
  • thenApply(Function<? super T,? extends U> fn) :对上一阶段的结果进行同步转换。

    java 复制代码
    CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
        CommonUtils.printLog("开始制作 生椰丝绒拿铁...");
        CommonUtils.secondSleep(1);
        CommonUtils.printLog("完成制作 生椰丝绒拿铁...");
        return "生椰丝绒拿铁";
    }, threadPool).thenApply(coffee -> {
        CommonUtils.printLog("开始制作 生椰丝绒拿铁...");
        CommonUtils.secondSleep(1);
        CommonUtils.printLog("生椰丝绒拿铁 制作完成...");
        return String.format("打包好的【%s】", coffee);
    });
    
    CommonUtils.printLog(future1.join());
  • thenAccept(Consumer<? super T> action):同步消费结果,无返回值。

java 复制代码
CompletableFuture<Void> future2 = future1
                .thenAcceptAsync(coffee -> CommonUtils.printLog("取走" + coffee), threadPool);
  • thenRun(Runnable action):上一阶段完成后同步执行一个动作,不关心其结果。
java 复制代码
future2.thenRunAsync(() -> CommonUtils.printLog("欢迎下次光临"), threadPool);

3.3 组合多个阶段

CompletableFuture 核心的能力在于能够将多个异步计算阶段灵活地组合起来,构建出复杂的异步工作流。这种组合主要解决两类问题:任务依赖任务聚合

3.3.1 链式依赖:thenCompose

  • 问题场景 :下一个异步任务依赖于 上一个任务的结果。使用 thenApply 会导致嵌套的 CompletableFuture<CompletableFuture<T>>
  • 解决方案thenCompose(Function<? super T,? extends CompletionStage<U>> fn) 用于将两个异步操作"扁平化"地链接起来。它接收一个函数,这个函数根据前一个任务的结果生成一个新的 CompletionStagethenCompose 会等待这个新的阶段完成,并直接返回其结果,从而避免嵌套。

以下用根据订单制作咖啡的例子来说明,整个制作流程包含2个异步任务:获取下一个订单getNextOrder和根据订单制作咖啡makeCoffee,而制作咖啡makeCoffee的异步任务依赖于获取下一个订单getNextOrder的任务结果。

java 复制代码
private static void completeCoffeeOrder() {
    ExecutorService threadPool = Executors.newFixedThreadPool(3);
    CompletableFuture<String> future = getNextOrder(threadPool)
            .thenCompose(order -> makeCoffee(order, threadPool));

    CommonUtils.printLog(future.join());
}

private static CompletableFuture<String> makeCoffee(Map<String, String> order, ExecutorService threadPool) {
    return CompletableFuture.supplyAsync(() -> {
        CommonUtils.printLog("根据订单获取需要制作的产品...");
        String productName = order.get("productName");
        CommonUtils.printLog(String.format("需要制作的产品为:【%s】", productName));
        CommonUtils.printLog(String.format("开始制作 【%s】...", productName));
        CommonUtils.secondSleep(1);
        CommonUtils.printLog(String.format("完成制作 【%s】...", productName));
        return String.format("❤%s", productName);
    }, threadPool);
}

private static CompletableFuture<Map<String, String>> getNextOrder(ExecutorService threadPool) {
    return CompletableFuture.supplyAsync(() -> {
        CommonUtils.printLog("获取下一个订单...");
        Map<String, String> order = new HashMap<>();
        order.put("orderId", "1");
        order.put("type", "外卖订单");
        order.put("productName", "生椰丝绒拿铁");
        return order;
    }, threadPool);
}

thenApply vs thenCompose 核心区别

  • thenApply(Function):接收一个同步 函数,对结果进行转换,返回值 U
  • thenCompose(Function):接收一个异步 函数,返回一个新的 CompletionStage<U>
特性 thenApply thenCompose
函数返回值 一个普通值 (U) 一个新的 CompletionStage<U>
最终结果 CompletableFuture<U> CompletableFuture<U>
类比 制作咖啡 -> 打包咖啡 (同步动作) 获取订单 -> 制作咖啡 (另一个异步任务)
常见误区 用于同步转换 用于链接依赖性的异步任务,解决嵌套问题

错误做法代码示例

java 复制代码
CompletableFuture<CompletableFuture<String>> future = getNextOrder(threadPool)
                .thenApply(order -> makeCoffee(order, threadPool));
CommonUtils.printLog(future.join().join());

3.3.2 结果聚合:thenCombine

  • 问题场景 :有两个独立的异步任务,需要等待它们都完成后,将两者的结果组合起来进行下一步计算。
  • 解决方案thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn) 将两个独立的 CompletionStage 的结果进行聚合。当两个阶段都正常完成后,使用提供的 BiFunction 来合并它们的结果,并返回一个持有合并后结果的新 CompletableFuture

如下是一个有2个产品的外卖订单,打包的时候需要等2个产品全部才能打包。

java 复制代码
private static void packOrder() {
     ExecutorService threadPool = Executors.newFixedThreadPool(3);

     CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
         CommonUtils.printLog("制作001号外卖订单第 1 杯咖啡:生椰丝绒拿铁");
         CommonUtils.secondSleep(1);
         return "生椰丝绒拿铁";
     }, threadPool).thenCombine(CompletableFuture.supplyAsync(() -> {
         CommonUtils.printLog("制作001号外卖订单第 2 杯咖啡:标准美式");
         CommonUtils.secondSleep(1);
         return "标准美式";
     }, threadPool), (c1, c2) -> {
         CommonUtils.printLog("打包001号外卖订单");
         return String.format("001号外卖订单:咖啡1:【%s】,咖啡2:【%s】", c1, c2);
     });

     CommonUtils.printLog(future.join());
 }

3.3.3 聚合所有:allOf

  • 问题场景 :你有多个独立的异步任务,不需要立即处理它们的结果,但需要等待所有任务都完成后,再执行某个操作。
  • 解决方案allOf(CompletableFuture<?>... cfs) 是一个静态方法,它接受一组 CompletableFuture,并返回一个新的 CompletableFuture<Void>。这个返回的 Future 会在所有输入的 Future 都完成后(无论是正常完成还是异常完成)才完成。它本身不包含任何结果,因此通常需要额外操作来收集各个任务的结果。

当你在商场突然想去看电影,但是还没吃午饭,而电影马上开始了,这时候就直接分别在咖啡店、奶茶店、汉堡店点了咖啡、奶茶、汉堡,你需要等所有的食物都做好才能去电影院,如下是该场景的代码示例:

java 复制代码
private static void waitLunch() {
    ExecutorService threadPool = Executors.newFixedThreadPool(3);

    CompletableFuture<String> future1 = waitFood("生椰丝绒拿铁", threadPool);
    CompletableFuture<String> future2 = waitFood("桃桃乌龙", threadPool);
    CompletableFuture<String> future3 = waitFood("麦辣鸡腿堡", threadPool);

    // 等待所有任务完成
    CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2, future3);

    // 在所有任务完成后,再提取各自的结果(此时join不会阻塞)
    CompletableFuture<List<String>> resultsFuture = allFutures.thenApply(v ->
            Stream.of(future1, future2, future3)
                    .map(CompletableFuture::join) // 重要:此时所有任务已完成,join立即返回
                    .collect(Collectors.toList())
    );

    List<String> allResults = resultsFuture.join(); // 等待并获取最终结果列表
    allResults.forEach(CommonUtils::printLog);

    threadPool.shutdown();
}

private static CompletableFuture<String> waitFood(String name, ExecutorService threadPool) {
    return CompletableFuture.supplyAsync(() -> {
        CommonUtils.printLog("等待 " + name);
        CommonUtils.secondSleep(1);
        return name;
    }, threadPool);
}

3.3.4 竞争任一:anyOf

  • 问题场景:你有多个执行相同工作的异步任务(例如,向多个镜像服务器请求同一份数据),你只关心最先返回的那个结果。
  • 解决方案anyOf(CompletableFuture<?>... cfs) 是一个静态方法,它接受一组 CompletableFuture,并返回一个新的 CompletableFuture<Object>。这个返回的 Future 会在任意一个输入的 Future 完成后就立刻完成,其结果就是那个最先完成的 Future 的结果。

当你看完电影想回家了,你当前所在的商场是繁华地段,商场直通地铁,且有2条线路都可以直达你所在的小区,那么你想尽快上地铁休息,那只需要等待最先达到的地铁,以下是代码示例:

java 复制代码
private static void waitSubway() {
    ExecutorService threadPool = Executors.newFixedThreadPool(3);

    CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
        CommonUtils.secondSleep(10);
        CommonUtils.printLog("1号线来了");
        return "1号线";
    }, threadPool);

    CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
        CommonUtils.secondSleep(3);
        CommonUtils.printLog("2号线来了");
        return "2号线";
    }, threadPool);

    CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(future1, future2);
    anyFuture.thenAccept(subway -> {
        CommonUtils.printLog(String.format("乘坐地铁【%s】: ", subway));
    });

    CommonUtils.printLog("end");
    threadPool.shutdown();
}

3.4 异常处理

异常处理是 CompletableFuture 流水线中至关重要的一环。异步计算中的异常无法用传统的 try-catch 块直接捕获,必须在链式调用中通过专门的方法进行处理,否则异常将被静默吞噬,导致调试极其困难。

CompletableFuture 提供了多种方法来管理和恢复来自异常完成状态的计算。

3.4.1 exceptionally(Function<Throwable,? extends T> fn)

提供一种简单的降级恢复机制。当上一阶段异常完成时,此方法会被触发,接收到的 Throwable 并允许返回一个降级值。如果上一阶段正常完成,则此方法不会被调用,原有结果将直接传递到下一阶段。

当咖啡店的生椰缺货了,无法再生产生椰丝绒拿铁,此时对于已经下单了生椰丝绒拿铁的订单,就必须做异常处理。

java 复制代码
private static void outOfStock() {
    ExecutorService threadPool = Executors.newFixedThreadPool(3);

    CompletableFuture<String> future = getNextOrder(threadPool)
            .thenApply(order -> {
                String productName = order.get("productName");
                if (productName.equals("生椰丝绒拿铁")) {
                    throw new RuntimeException("没有生椰了,无法制作");
                }
                CommonUtils.printLog(String.format("准备制作【%s】", productName));
                return productName;
            }).exceptionally(e -> {
                CommonUtils.printLog("下架生椰丝绒拿铁");
                CommonUtils.printLog("联系顾客更换饮品或退单,并补充9.9元优惠券");
                return "Fallback Value";
            });

    CommonUtils.printLog(future.join());
}

发生异常,exceptionally方法被处罚,并返回一个降级值。

console 复制代码
2025-08-20 12:49:20:020 - 【pool-1-thread-1 - 24 】 - 获取下一个订单...
2025-08-20 12:49:20:020 - 【pool-1-thread-1 - 24 】 - 下架生椰丝绒拿铁
2025-08-20 12:49:20:020 - 【pool-1-thread-1 - 24 】 - 联系顾客更换饮品或退单,并补充9.9元优惠券
2025-08-20 12:49:20:020 - 【main -  1 】 - Fallback Value

不使用exceptionally接受异常

java 复制代码
private static void outOfStock() {
    ExecutorService threadPool = Executors.newFixedThreadPool(3);

    CompletableFuture<String> future = getNextOrder(threadPool)
            .thenApply(order -> {
                String productName = order.get("productName");
                if (productName.equals("生椰丝绒拿铁")) {
                    throw new RuntimeException("没有生椰了,无法制作");
                }
                CommonUtils.printLog(String.format("准备制作【%s】", productName));
                return productName;
            });

    CommonUtils.printLog(future.join());
}
console 复制代码
2025-08-20 12:47:08:008 - 【pool-1-thread-1 - 24 】 - 获取下一个订单...
Exception in thread "main" java.util.concurrent.CompletionException: java.lang.RuntimeException: 没有生椰了,无法制作
	at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:315)
	at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:320)
	at java.base/java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:649)
	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:842)
Caused by: java.lang.RuntimeException: 没有生椰了,无法制作
	at cn.shgang.CompletableFutureStudy.lambda$outOfStock$0(CompletableFutureStudy.java:24)
	at java.base/java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:646)
	... 5 more

3.4.2 handle(BiFunction<? super T, Throwable, ? extends U> fn)

无论正常完成还是异常完成都会被调用,可根据结果和异常情况进行统一处理。。它接收两个参数:结果(正常时为值,异常时为null)和异常(正常时为null,异常时为Throwable)。该方法必须返回一个新结果,从而可以统一处理成功和失败的情况。

java 复制代码
private static void handleException() {
    ExecutorService threadPool = Executors.newFixedThreadPool(3);

    CompletableFuture<String> future = getNextOrder(threadPool)
            .thenApply(order -> {
                String productName = order.get("productName");
                if (productName.equals("生椰丝绒拿铁")) {
                    throw new RuntimeException("没有生椰了,无法制作");
                }
                CommonUtils.printLog(String.format("准备制作【%s】", productName));
                return productName;
            }).handle((result, e) -> {
                if (e != null) {
                    CommonUtils.printLog("下架生椰丝绒拿铁");
                    CommonUtils.printLog("联系顾客更换饮品或退单,并补充9.9元优惠券");
                    return "顾客退单或更换其他饮品";
                }
                return String.format("制作【%s】", result);
            });

    CommonUtils.printLog(future.join());
}

3.4.3 whenComplete(BiConsumer<? super T, ? super Throwable> action)

此方法会接收结果和异常,执行一个动作,但不会改变最终结果 。如果其中的消费者动作自身抛出异常,会导致返回的 CompletableFuture 异常完成。用于添加副作用(Side-effect),如日志记录、指标上报等。

java 复制代码
private static void whenCompleteEx() {
    ExecutorService threadPool = Executors.newFixedThreadPool(3);

    CompletableFuture<String> future = getNextOrder(threadPool)
            .thenApply(order -> {
                String productName = order.get("productName");
                if (productName.equals("生椰丝绒拿铁")) {
                    throw new RuntimeException("没有生椰了,无法制作");
                }
                CommonUtils.printLog(String.format("准备制作【%s】", productName));
                return productName;
            }).whenComplete((result, e) -> {
                if (e != null) {
                    CommonUtils.printLog("下架生椰丝绒拿铁");
                    CommonUtils.printLog("联系顾客更换饮品或退单,并补充9.9元优惠券");
                } else {
                    CommonUtils.printLog(String.format("制作【%s】", result));
                }
            });

    CommonUtils.printLog(future.join());
}
comsole 复制代码
2025-08-20 13:04:53:053 - 【pool-1-thread-1 - 24 】 - 获取下一个订单...
2025-08-20 13:04:53:053 - 【pool-1-thread-1 - 24 】 - 下架生椰丝绒拿铁
2025-08-20 13:04:53:053 - 【pool-1-thread-1 - 24 】 - 联系顾客更换饮品或退单,并补充9.9元优惠券
Exception in thread "main" java.util.concurrent.CompletionException: java.lang.RuntimeException: 没有生椰了,无法制作
	at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:315)
	at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:320)
	at java.base/java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:649)
	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:842)
Caused by: java.lang.RuntimeException: 没有生椰了,无法制作
	at cn.shgang.CompletableFutureStudy.lambda$main$0(CompletableFutureStudy.java:21)
	at java.base/java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:646)
	... 5 more

3.5 获取结果

  • get():阻塞直到完成,抛出受检异常。
  • join() :阻塞直到完成,抛出未受检的 CompletionException。更适合在 Lambda 表达式或链式调用中使用。
  • getNow(T valueIfAbsent):非阻塞地获取结果,如果未完成则返回提供的默认值。

3.6 超时控制

Java 9 为 CompletableFuture 引入了两个超时相关的核心方法。

3.6.1 orTimeout(long timeout, TimeUnit unit)

CompletableFuture 设置一个超时时间。如果在指定时间内原始 Future 没有完成,则该方法返回的 Future 将以 TimeoutException 异常完成。原始任务仍然可能在后台继续运行

代码示例:

java 复制代码
private static void orTimeout() {
    ExecutorService threadPool = Executors.newFixedThreadPool(3);

    CompletableFuture<Map<String, String>> future = CompletableFuture.supplyAsync(() -> {
        CommonUtils.printLog("获取下一个订单...");
        // 模拟网络延迟
        CommonUtils.secondSleep(5);
        Map<String, String> order = new HashMap<>();
        order.put("orderId", "1");
        order.put("type", "外卖订单");
        order.put("productName", "生椰丝绒拿铁");
        order.forEach((k, v) -> {
            CommonUtils.printLog(k + ": " + v);
        });
        return order;
    }, threadPool).orTimeout(2, TimeUnit.SECONDS);

    try {
        Map<String, String> order = future.join();
        order.forEach((k, v) -> {
            CommonUtils.printLog(k + ": " + v);
        });
    } catch (Exception e) {
        if (e.getCause() instanceof TimeoutException) {
            CommonUtils.printLog("Task timed out!");
        }
    }

    CommonUtils.printLog("end");
}

运行结果:

console 复制代码
2025-08-20 14:54:23:023 - 【pool-1-thread-1 - 24 】 - 获取下一个订单...
2025-08-20 14:54:25:025 - 【main -  1 】 - Task timed out!
2025-08-20 14:54:25:025 - 【main -  1 】 - end
2025-08-20 14:54:28:028 - 【pool-1-thread-1 - 24 】 - orderId: 1
2025-08-20 14:54:28:028 - 【pool-1-thread-1 - 24 】 - type: 外卖订单
2025-08-20 14:54:28:028 - 【pool-1-thread-1 - 24 】 - productName: 生椰丝绒拿铁

从结果可以看出,即使已经超时,但是任务依然在运行。

3.6.2 completeOnTimeout(T value, long timeout, TimeUnit unit)

CompletableFuture 设置一个超时时间和一个默认值。如果在指定时间内原始 Future 没有完成,则该方法返回的 Future 将被赋予这个默认值并正常完成。同样,原始任务可能仍在运行

代码示例:

java 复制代码
private static void completeOnTimeout() {
    ExecutorService threadPool = Executors.newFixedThreadPool(3);

    CompletableFuture<Map<String, String>> future = CompletableFuture.supplyAsync(() -> {
        CommonUtils.printLog("获取下一个订单...");
        // 模拟网络延迟
        CommonUtils.secondSleep(5);
        Map<String, String> order = new HashMap<>();
        order.put("orderId", "1");
        order.put("type", "外卖订单");
        order.put("productName", "生椰丝绒拿铁");
        order.forEach((k, v) -> {
            CommonUtils.printLog(k + ": " + v);
        });
        return order;
    }, threadPool).completeOnTimeout(null,2, TimeUnit.SECONDS);

    Map<String, String> order = future.join();
    if (order == null) {
        CommonUtils.printLog("Task timed out! 返回默认值 null");
    }

    CommonUtils.printLog("end");
}

运行结果:

console 复制代码
2025-08-20 15:00:50:050 - 【pool-1-thread-1 - 24 】 - 获取下一个订单...
2025-08-20 15:00:52:052 - 【main -  1 】 - Task timed out! 返回默认值 null
2025-08-20 15:00:52:052 - 【main -  1 】 - end
2025-08-20 15:00:55:055 - 【pool-1-thread-1 - 24 】 - orderId: 1
2025-08-20 15:00:55:055 - 【pool-1-thread-1 - 24 】 - type: 外卖订单
2025-08-20 15:00:55:055 - 【pool-1-thread-1 - 24 】 - productName: 生椰丝绒拿铁

3.6.3 Java8 中超时控制

在 Java 8 中,需要利用 ScheduledExecutorService 来主动完成 Future,从而实现超时控制。

方案原理 :启动一个调度任务,在超时时间后,如果发现原始 Future 仍未完成,则手动调用 completeExceptionally 使其以 TimeoutException 完成。 代码示例:

java 复制代码
// 创建一个用于超时控制的调度线程池(通常一个应用共享一个即可)
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

private static void timeoutJava8() {
    ExecutorService threadPool = Executors.newFixedThreadPool(3);
    CompletableFuture<Map<String, String>> future = CompletableFuture.supplyAsync(() -> {
        CommonUtils.printLog("获取下一个订单...");
        // 模拟网络延迟
        CommonUtils.secondSleep(5);
        Map<String, String> order = new HashMap<>();
        order.put("orderId", "1");
        order.put("type", "外卖订单");
        order.put("productName", "生椰丝绒拿铁");
        order.forEach((k, v) -> {
            CommonUtils.printLog(k + ": " + v);
        });
        return order;
    }, threadPool);

    CompletableFuture<Map<String, String>> future1 = withTimeout(future, 1, TimeUnit.SECONDS, scheduler);

    try {
        Map<String, String> order = future1.join();
    } catch (Exception e) {
        if (e.getCause() instanceof TimeoutException) {
            CommonUtils.printLog("Task timed out!");
        }
    }
    CommonUtils.printLog("end");
}

public static  <T> CompletableFuture<T> withTimeout(CompletableFuture<T> future, long timeout, TimeUnit timeUnit, ScheduledExecutorService scheduler) {
    // 创建一个新的CompletableFuture,用于返回
    CompletableFuture<T> timeoutFuture = new CompletableFuture<>();

    // 启动一个调度任务,在超时后让timeoutFuture以TimeoutException完成
    ScheduledFuture<?> scheduledTask = scheduler.schedule(() -> {
        if (!future.isDone()) {
            timeoutFuture.completeExceptionally(new TimeoutException("Operation timed out"));
        }
    }, timeout, timeUnit);

    // 无论原始Future成功还是失败,都取消调度任务,避免不必要的执行
    future.whenComplete((result, ex) -> {
        scheduledTask.cancel(true); // 中断调度任务
        if (ex != null) {
            timeoutFuture.completeExceptionally(ex);
        } else {
            timeoutFuture.complete(result);
        }
    });

    return timeoutFuture;
}

总结

CompletableFuture 通过丰富的链式调用与组合操作,为我们提供了强大的异步编程能力。从创建任务、处理结果、组合多个阶段,到异常恢复与超时控制,每一个环节都体现了其设计的精妙与实用。掌握它,不仅能写出更简洁高效的并发代码,还能有效避免回调地狱和线程阻塞问题。

相关推荐
喵手9 分钟前
如何利用Java的Stream API提高代码的简洁度和效率?
java·后端·java ee
-Xie-10 分钟前
Maven(二)
java·开发语言·maven
IT利刃出鞘22 分钟前
Java线程的6种状态和JVM状态打印
java·开发语言·jvm
天天摸鱼的java工程师1 小时前
Java 解析 JSON 文件:八年老开发的实战总结(从业务到代码)
java·后端·面试
白仑色1 小时前
Spring Boot 全局异常处理
java·spring boot·后端·全局异常处理·统一返回格式
喵手1 小时前
反射机制:你真的了解它的“能力”吗?
java·后端·java ee
kaika12 小时前
告别复杂配置!使用 1Panel 运行环境功能轻松搭建 Java 应用
java·1panel·建站·halo
有梦想的攻城狮2 小时前
Java 11中的Collections类详解
java·windows·python·java11·collections
六千江山2 小时前
从字符串中提取符合规则的汽车车牌
java
33255_40857_280592 小时前
从韩立结婴看Java进阶:一个10年老码农的修仙式成长指南
java