前言
随着异步编程在现代高并发系统中的地位日益提升,Java 8 引入的 CompletableFuture
成为处理异步任务编排的重要工具。它不仅弥补了传统 Future
在回调机制和组合能力上的不足,还通过函数式风格 API 极大地提升了代码的可读性与可维护性。本文介绍了 CompletableFuture
中关于异步任务编排、异常处理与超时控制等实用的核心 API。
1. CompletableFuture背景概述
CompletableFuture
自 Java 8 引入,是 java.util.concurrent
包下对 Future
接口的增强实现,同时实现了 CompletionStage
接口。在Java 8之前,一般通过Future实现异步。
Future
用于表示异步计算的结果,只能通过阻塞或轮询的方式获取结果,而且不支持设置回调方法,若要设置回调一般使用guava的ListenableFuture,又引入了回调地狱问题。CompletableFuture
对Future
进行了扩展,提供了更丰富的API用于:- 支持回调处理计算结果
- 支持组合操作和异步编排
- 解决回调地狱问题
2. 核心概念
2.1 CompletionStage
接口

CompletableFuture
实现了2个接口:Future
和CompletionStage
。Future
表示异步计算的结果,CompletionStage
表示异步执行的一个步骤(Stage),这个步骤可能是由另外一个CompletionStage
触发的,对着当前步骤的完成,也可能触发其他一系列CompletionStage
的执行。从而我们可以根据实际业务对这些步骤进行编排组合,CompletionStage
接口正式定义了这样的能力,我们可以通过其提供的thenApply
、thenCompose
等函数式编程方法来组合编排这些步骤。
2.2 执行与完成
- 异步执行 :通过
supplyAsync
或runAsync
方法提交任务至线程池执行,并在任务完成后自动标记完成。 - 显式完成 :在任何线程中,可通过
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)
:对上一阶段的结果进行同步转换。javaCompletableFuture<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)
用于将两个异步操作"扁平化"地链接起来。它接收一个函数,这个函数根据前一个任务的结果生成一个新的CompletionStage
。thenCompose
会等待这个新的阶段完成,并直接返回其结果,从而避免嵌套。
以下用根据订单制作咖啡的例子来说明,整个制作流程包含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
通过丰富的链式调用与组合操作,为我们提供了强大的异步编程能力。从创建任务、处理结果、组合多个阶段,到异常恢复与超时控制,每一个环节都体现了其设计的精妙与实用。掌握它,不仅能写出更简洁高效的并发代码,还能有效避免回调地狱和线程阻塞问题。