写在前面
大家好,欢迎来到JDK8新特性系列教程的第10天!在前面的学习中,我们已经掌握了Lambda表达式、Stream API、Optional、接口默认方法、重复注解等核心特性。今天,我们将学习JDK8中最强大的并发工具之一------CompletableFuture。
在JDK5引入的Future接口虽然提供了基本的异步任务支持,但它存在明显局限:无法方便地获取结果、不能链式组合多个异步任务、异常处理困难。CompletableFuture彻底解决了这些问题,提供了函数式、声明式的异步编程模型,让复杂的异步流程变得清晰优雅。
无论你是处理高并发Web请求、构建响应式系统,还是优化批处理任务,CompletableFuture都是必备技能。让我们开始今天的学习!

目录
-
- 写在前面
- 一、异步编程的意义
-
- [1.1 为什么需要异步编程](#1.1 为什么需要异步编程)
- [1.2 异步编程的优势](#1.2 异步编程的优势)
- [1.3 实际场景举例](#1.3 实际场景举例)
- 二、Future接口的局限性
-
- [2.1 Future的基本用法](#2.1 Future的基本用法)
- [2.2 Future的局限性](#2.2 Future的局限性)
- [2.3 复杂场景的困境](#2.3 复杂场景的困境)
- 三、CompletableFuture的创建
-
- [3.1 基本创建方式](#3.1 基本创建方式)
- [3.2 使用自定义线程池](#3.2 使用自定义线程池)
- [3.3 手动控制完成](#3.3 手动控制完成)
- 四、异步回调
-
- [4.1 转换结果:thenApply](#4.1 转换结果:thenApply)
- [4.2 消费结果:thenAccept](#4.2 消费结果:thenAccept)
- [4.3 纯副作用:thenRun](#4.3 纯副作用:thenRun)
- [4.4 回调方法对比](#4.4 回调方法对比)
- [4.5 异步版本](#4.5 异步版本)
- [4.6 异常处理](#4.6 异常处理)
- [4.7 异常处理方法对比](#4.7 异常处理方法对比)
- 五、异步组合
-
- [5.1 串行组合:thenCompose](#5.1 串行组合:thenCompose)
- [5.2 并行组合:thenCombine](#5.2 并行组合:thenCombine)
- [5.3 等待多个任务:allOf](#5.3 等待多个任务:allOf)
- [5.4 获取最快结果:anyOf](#5.4 获取最快结果:anyOf)
- [5.5 组合方法对比](#5.5 组合方法对比)
- 六、异常处理和超时控制
-
- [6.1 完整的异常处理策略](#6.1 完整的异常处理策略)
- [6.2 JDK9+的超时控制](#6.2 JDK9+的超时控制)
- [6.3 JDK8的超时实现](#6.3 JDK8的超时实现)
- 七、踩坑提醒与经验之谈
-
- [7.1 坑点一:默认线程池是ForkJoinPool.commonPool](#7.1 坑点一:默认线程池是ForkJoinPool.commonPool)
- [7.2 坑点二:注意线程池隔离](#7.2 坑点二:注意线程池隔离)
- [7.3 坑点三:异常被吞掉](#7.3 坑点三:异常被吞掉)
- [7.4 坑点四:join() vs get()](#7.4 坑点四:join() vs get())
- [7.5 经验之谈](#7.5 经验之谈)
- 八、面试高频考点
- 九、总结
- 参考资料
- 互动话题
一、异步编程的意义
1.1 为什么需要异步编程
在现代软件开发中,异步编程已经成为提升系统性能的关键技术:
同步调用:
[请求1]=====[等待IO]=====[处理结果]
[请求2]=====[等待IO]=====[处理结果]
[请求3]=====[等待IO]=====[处理结果]
异步调用:
[请求1]--发起-->[等待IO]
[请求2]--发起-->[等待IO] --回调-->[处理结果1,2,3]
[请求3]--发起-->[等待IO]
1.2 异步编程的优势
| 优势 | 说明 | 典型场景 |
|---|---|---|
| 提高吞吐量 | 不阻塞线程,一个线程处理多个请求 | Web服务器 |
| 降低延迟 | 并行执行多个独立任务 | 聚合多个服务数据 |
| 资源高效 | 减少线程创建,降低上下文切换 | 高并发系统 |
| 响应性 | UI线程不被阻塞,保持界面流畅 | 桌面/移动应用 |
1.3 实际场景举例
场景1:电商订单查询
java
// 同步方式:串行执行,总耗时 = 100 + 80 + 50 = 230ms
public OrderDetail getOrderDetail(String orderId) {
Order order = orderService.getOrder(orderId); // 100ms
User user = userService.getUser(order.getUserId()); // 80ms
List<Item> items = itemService.getItems(orderId); // 50ms
return new OrderDetail(order, user, items);
}
// 异步方式:并行执行,总耗时 ≈ max(100, 80, 50) = 100ms
public CompletableFuture<OrderDetail> getOrderDetailAsync(String orderId) {
CompletableFuture<Order> orderFuture = CompletableFuture
.supplyAsync(() -> orderService.getOrder(orderId));
CompletableFuture<User> userFuture = CompletableFuture
.supplyAsync(() -> userService.getUser(orderId));
CompletableFuture<List<Item>> itemsFuture = CompletableFuture
.supplyAsync(() -> itemService.getItems(orderId));
return CompletableFuture.allOf(orderFuture, userFuture, itemsFuture)
.thenApply(v -> new OrderDetail(
orderFuture.join(),
userFuture.join(),
itemsFuture.join()
));
}
二、Future接口的局限性
2.1 Future的基本用法
JDK5引入的Future接口提供了基本的异步支持:
java
ExecutorService executor = Executors.newFixedThreadPool(4);
// 提交异步任务
Future<String> future = executor.submit(() -> {
Thread.sleep(1000);
return "Hello";
});
// 获取结果(阻塞!)
String result = future.get(); // 阻塞直到结果返回
System.out.println(result);
executor.shutdown();
2.2 Future的局限性
| 局限性 | 说明 | 影响 |
|---|---|---|
| get()阻塞 | 获取结果时阻塞当前线程 | 失去异步意义 |
| 无法链式调用 | 不能方便地组合多个Future | 代码复杂 |
| 异常处理困难 | 异常在get()时才抛出 | 难以定位问题 |
| 无法手动完成 | 不能从外部设置结果 | 灵活性差 |
| 没有回调机制 | 不能设置完成后的操作 | 需要轮询 |
2.3 复杂场景的困境
假设需要实现:获取用户 -> 获取订单 -> 计算总价
java
// 使用Future的丑陋实现
Future<User> userFuture = executor.submit(() -> getUser(userId));
User user = userFuture.get(); // 阻塞
Future<List<Order>> ordersFuture = executor.submit(() -> getOrders(user.getId()));
List<Order> orders = ordersFuture.get(); // 阻塞
Future<BigDecimal> totalFuture = executor.submit(() -> calculateTotal(orders));
BigDecimal total = totalFuture.get(); // 阻塞
// 代码嵌套深、异常处理复杂、难以维护
三、CompletableFuture的创建
3.1 基本创建方式
CompletableFuture提供了多种创建方式:
java
// 方式1:runAsync - 无返回值
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
System.out.println("异步任务,无返回值");
});
// 方式2:supplyAsync - 有返回值
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
return "Hello, CompletableFuture!";
});
// 方式3:completedFuture - 已完成
CompletableFuture<String> future3 = CompletableFuture.completedFuture("已完成");
// 方式4:failedFuture - 已失败(JDK9+)
CompletableFuture<String> future4 = CompletableFuture.failedFuture(
new RuntimeException("失败")
);
3.2 使用自定义线程池
java
// 创建自定义线程池
ExecutorService executor = new ThreadPoolExecutor(
4, // 核心线程数
8, // 最大线程数
60, // 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(100), // 任务队列
new ThreadFactory() { // 线程工厂
private final AtomicInteger count = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "custom-pool-" + count.incrementAndGet());
}
},
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
// 使用自定义线程池
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("执行线程: " + Thread.currentThread().getName());
return "使用自定义线程池";
}, executor);
3.3 手动控制完成
java
CompletableFuture<String> future = new CompletableFuture<>();
// 在其他线程中完成
new Thread(() -> {
try {
Thread.sleep(1000);
future.complete("任务完成"); // 正常完成
// future.completeExceptionally(new RuntimeException("失败")); // 异常完成
} catch (InterruptedException e) {
future.completeExceptionally(e);
}
}).start();
// 取消任务
// future.cancel(true);
String result = future.get();
System.out.println(result);
四、异步回调
4.1 转换结果:thenApply
java
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");
// thenApply: 接收上一步结果,转换后返回
CompletableFuture<String> result = future
.thenApply(s -> s + " World")
.thenApply(String::toUpperCase);
System.out.println(result.join()); // HELLO WORLD
4.2 消费结果:thenAccept
java
CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World")
.thenAccept(System.out::println); // Hello World,无返回值
4.3 纯副作用:thenRun
java
CompletableFuture.supplyAsync(() -> "Hello")
.thenRun(() -> System.out.println("任务完成,不依赖结果"));
4.4 回调方法对比
| 方法 | 输入 | 输出 | 用途 |
|---|---|---|---|
thenApply |
上一步结果 | 转换后的结果 | 数据转换 |
thenAccept |
上一步结果 | Void | 消费结果 |
thenRun |
无 | Void | 执行副作用 |
4.5 异步版本
每个回调方法都有对应的异步版本(带Async后缀):
java
ExecutorService executor = Executors.newFixedThreadPool(4);
CompletableFuture.supplyAsync(() -> {
System.out.println("supplyAsync: " + Thread.currentThread().getName());
return "Hello";
}, executor)
.thenApply(s -> {
System.out.println("thenApply: " + Thread.currentThread().getName());
return s + " World";
})
.thenApplyAsync(s -> { // 使用默认线程池
System.out.println("thenApplyAsync: " + Thread.currentThread().getName());
return s.toUpperCase();
})
.thenApplyAsync(s -> { // 使用自定义线程池
System.out.println("thenApplyAsync(custom): " + Thread.currentThread().getName());
return s + "!";
}, executor)
.thenAccept(System.out::println);
4.6 异常处理
java
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (true) {
throw new RuntimeException("出错了!");
}
return "Hello";
});
// exceptionally: 捕获异常,提供默认值
CompletableFuture<String> withFallback = future
.exceptionally(ex -> {
System.out.println("异常: " + ex.getMessage());
return "默认值";
});
System.out.println(withFallback.join()); // 默认值
// whenComplete: 无论成功失败都执行
CompletableFuture<String> withLog = future
.whenComplete((result, ex) -> {
if (ex != null) {
System.out.println("执行失败: " + ex.getMessage());
} else {
System.out.println("执行成功: " + result);
}
});
// handle: 统一处理成功和失败
CompletableFuture<String> handled = future
.handle((result, ex) -> {
if (ex != null) {
return "错误: " + ex.getMessage();
}
return result;
});
4.7 异常处理方法对比
| 方法 | 成功时 | 失败时 | 返回值 |
|---|---|---|---|
exceptionally |
原样返回 | 执行回调,提供默认值 | CompletableFuture |
whenComplete |
执行回调 | 执行回调 | CompletableFuture(不改变结果) |
handle |
执行回调 | 执行回调 | CompletableFuture++(可转换类型)++ |
五、异步组合
5.1 串行组合:thenCompose
当一个异步操作的结果作为下一个异步操作的输入时使用:
java
// 场景:先获取用户,再根据用户获取订单
public CompletableFuture<User> getUserAsync(String userId) {
return CompletableFuture.supplyAsync(() -> userService.getUser(userId));
}
public CompletableFuture<List<Order>> getOrdersAsync(String userId) {
return CompletableFuture.supplyAsync(() -> orderService.getOrders(userId));
}
// 使用thenCompose串行执行
CompletableFuture<List<Order>> ordersFuture = getUserAsync("user123")
.thenCompose(user -> getOrdersAsync(user.getId()));
// 对比:使用thenApply会得到嵌套的CompletableFuture
CompletableFuture<CompletableFuture<List<Order>>> nested = getUserAsync("user123")
.thenApply(user -> getOrdersAsync(user.getId())); // 不要这样用!
5.2 并行组合:thenCombine
当两个异步操作独立执行,需要合并结果时使用:
java
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
sleep(100);
return "Hello";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
sleep(200);
return "World";
});
// thenCombine: 等待两个都完成,合并结果
CompletableFuture<String> combined = future1
.thenCombine(future2, (s1, s2) -> s1 + " " + s2);
System.out.println(combined.join()); // Hello World
5.3 等待多个任务:allOf
java
CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
sleep(100);
return "任务1";
});
CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
sleep(200);
return "任务2";
});
CompletableFuture<String> task3 = CompletableFuture.supplyAsync(() -> {
sleep(150);
return "任务3";
});
// allOf: 等待所有完成
CompletableFuture<Void> all = CompletableFuture.allOf(task1, task2, task3);
// 等待所有任务完成
all.join();
// 获取各个结果
System.out.println(task1.join()); // 任务1
System.out.println(task2.join()); // 任务2
System.out.println(task3.join()); // 任务3
5.4 获取最快结果:anyOf
java
CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
sleep(300);
return "来自服务A";
});
CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
sleep(100);
return "来自服务B";
});
CompletableFuture<String> task3 = CompletableFuture.supplyAsync(() -> {
sleep(200);
return "来自服务C";
});
// anyOf: 返回最先完成的
CompletableFuture<Object> any = CompletableFuture.anyOf(task1, task2, task3);
System.out.println(any.join()); // 来自服务B(最快)
5.5 组合方法对比
| 方法 | 场景 | 特点 |
|---|---|---|
thenCompose |
串行依赖 | 扁平化嵌套Future |
thenCombine |
并行+合并 | 两个任务独立执行 |
allOf |
等待所有 | 返回Void,需单独获取结果 |
anyOf |
获取最快 | 返回Object,需类型转换 |
六、异常处理和超时控制
6.1 完整的异常处理策略
java
public CompletableFuture<String> robustAsyncOperation() {
return CompletableFuture.supplyAsync(() -> {
// 可能抛出异常的操作
return riskyOperation();
})
.thenApply(result -> {
// 转换结果,可能抛出异常
return transform(result);
})
.exceptionally(ex -> {
// 捕获所有异常,提供默认值
log.error("操作失败", ex);
return "默认值";
})
.whenComplete((result, ex) -> {
// 记录日志,无论成功失败
log.info("操作完成,结果: {}", result);
});
}
6.2 JDK9+的超时控制
java
// JDK9+ 提供了orTimeout和completeOnTimeout
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
sleep(5000); // 耗时5秒
return "结果";
})
.orTimeout(3, TimeUnit.SECONDS) // 3秒超时抛出TimeoutException
.exceptionally(ex -> "超时默认值");
// completeOnTimeout: 超时提供默认值
CompletableFuture<String> withTimeout = CompletableFuture.supplyAsync(() -> {
sleep(5000);
return "结果";
})
.completeOnTimeout("默认值", 3, TimeUnit.SECONDS);
6.3 JDK8的超时实现
java
public static <T> CompletableFuture<T> withTimeout(
CompletableFuture<T> future,
long timeout,
TimeUnit unit) {
CompletableFuture<T> timeoutFuture = new CompletableFuture<>();
// 延迟任务,超时后完成
Delayer.delay(() -> timeoutFuture.completeExceptionally(
new TimeoutException("操作超时")
), timeout, unit);
// 返回先完成的那个
return (CompletableFuture<T>) CompletableFuture.anyOf(future, timeoutFuture);
}
// 使用
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
sleep(5000);
return "结果";
});
CompletableFuture<String> withTimeout = withTimeout(future, 3, TimeUnit.SECONDS)
.exceptionally(ex -> "超时");
七、踩坑提醒与经验之谈
7.1 坑点一:默认线程池是ForkJoinPool.commonPool
java
// 危险!使用默认线程池
CompletableFuture.supplyAsync(() -> {
// 默认使用ForkJoinPool.commonPool()
// 线程数 = CPU核心数 - 1
return doSomething();
});
// 问题:
// 1. 线程数有限,可能耗尽
// 2. 阻塞操作会占用线程,影响其他任务
// 3. 无法自定义线程名、拒绝策略等
经验 :生产环境始终使用自定义线程池
java
private static final ExecutorService executor = new ThreadPoolExecutor(
4, 8, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
new ThreadFactoryBuilder().setNameFormat("async-pool-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
// 使用自定义线程池
CompletableFuture.supplyAsync(() -> doSomething(), executor);
7.2 坑点二:注意线程池隔离
java
// 错误:CPU密集型任务和IO密集型任务混用线程池
ExecutorService mixedPool = Executors.newFixedThreadPool(10);
// CPU密集型任务
CompletableFuture.supplyAsync(() -> heavyComputation(), mixedPool);
// IO密集型任务
CompletableFuture.supplyAsync(() -> callRemoteService(), mixedPool);
经验:根据任务类型使用不同的线程池
java
// CPU密集型:线程数 = CPU核心数
ExecutorService cpuPool = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()
);
// IO密集型:线程数可以更多
ExecutorService ioPool = new ThreadPoolExecutor(
10, 50, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000)
);
7.3 坑点三:异常被吞掉
java
// 危险:异常被吞掉,无法感知
CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("出错了");
});
// 没有后续处理,异常被忽略!
// 正确:必须处理异常
CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("出错了");
})
.exceptionally(ex -> {
log.error("异常", ex);
return "默认值";
});
// 或者等待获取结果(会抛出异常)
try {
future.get();
} catch (Exception e) {
log.error("异常", e);
}
7.4 坑点四:join() vs get()
java
// get():抛出受检异常,需要try-catch
try {
String result = future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
// join():抛出非受检异常,代码更简洁
String result = future.join();
// 但要注意:join()会包装异常
// ExecutionException -> CompletionException
7.5 经验之谈
- 始终使用自定义线程池:默认的common pool不适合生产环境
- 注意异常处理:避免异常被静默吞掉
- 合理使用方法 :
thenApply用于转换,thenCompose用于链式,thenCombine用于合并 - 避免阻塞操作 :在CompletableFuture链中避免使用
get()/join()阻塞 - 资源释放:应用关闭时记得关闭线程池
八、面试高频考点
考点一:CompletableFuture和Future的区别?
答:
| 特性 | Future | CompletableFuture |
|---|---|---|
| 获取结果 | get()阻塞 | 支持回调,非阻塞 |
| 链式调用 | 不支持 | 支持thenApply等链式操作 |
| 组合能力 | 弱 | 支持thenCombine、allOf等 |
| 异常处理 | 困难 | exceptionally、handle等 |
| 手动完成 | 不支持 | 支持complete() |
| 编程模型 | 命令式 | 函数式、声明式 |
考点二:如何处理异步异常?
答:CompletableFuture提供了三种异常处理方式:
-
exceptionally:捕获异常,提供默认值
javafuture.exceptionally(ex -> "默认值"); -
whenComplete:无论成功失败都执行,不改变结果
javafuture.whenComplete((result, ex) -> { /* 记录日志 */ }); -
handle:统一处理成功和失败,可以转换结果类型
javafuture.handle((result, ex) -> ex != null ? "错误" : result);
考点三:thenApply和thenCompose的区别?
答:
-
thenApply:用于转换结果,返回普通值
javafuture.thenApply(s -> s.toUpperCase()) // CompletableFuture<String> -
thenCompose:用于链式调用,返回另一个CompletableFuture(扁平化)
javafuture.thenCompose(s -> asyncOperation(s)) // CompletableFuture<String>
如果用thenApply返回CompletableFuture,会得到嵌套的CompletableFuture<CompletableFuture<T>>。
考点四:allOf和anyOf的区别?
答:
- allOf :等待所有任务完成,返回
CompletableFuture<Void> - anyOf :返回最先完成的任务,返回
CompletableFuture<Object>
注意:allOf返回的是Void,需要单独通过各个Future获取结果;anyOf返回的是Object,需要类型转换。
考点五:CompletableFuture的默认线程池是什么?有什么问题?
答 :默认使用ForkJoinPool.commonPool(),线程数等于CPU核心数 - 1。
问题:
- 线程数有限,容易耗尽
- 阻塞操作会占用线程,影响其他任务
- 无法自定义线程名、拒绝策略
建议:生产环境始终使用自定义线程池。
九、总结
今天我们深入学习了CompletableFuture异步编程:
- 异步编程意义:提高吞吐量、降低延迟、资源高效利用
- Future的局限:get阻塞、不能链式、异常处理困难
- 创建方式:runAsync、supplyAsync、completedFuture
- 异步回调:thenApply、thenAccept、thenRun及其Async版本
- 异常处理:exceptionally、whenComplete、handle
- 异步组合:thenCompose串行、thenCombine并行、allOf等待全部、anyOf获取最快
- 超时控制:JDK9+的orTimeout/completeOnTimeout,JDK8的自定义实现
- 踩坑提醒:默认线程池问题、线程池隔离、异常处理
下一步预告
Day11:新工具类
JDK8还带来了许多实用的新工具类:
- Objects工具类的空安全操作
- StringJoiner和String.join
- Base64编码解码
- 新的日期时间API(LocalDate、LocalTime、LocalDateTime)
- Optional的更多用法
敬请期待!
参考资料
互动话题
-
你在项目中使用过CompletableFuture吗? 是用来解决什么场景的?欢迎在评论区分享你的使用经验。
-
思考题:假设你需要同时调用3个外部API获取数据,只要任意2个返回就可以继续处理,你会如何用CompletableFuture实现?
-
踩坑分享:你在使用CompletableFuture时遇到过什么坑?有没有被默认线程池"坑"过的经历?
如果这篇文章对你有帮助,请点赞、收藏、转发支持一下!关注专栏,持续学习JDK8新特性!
Day10打卡:CompletableFuture异步编程 ✅