java 异步编程
Q1:CompletableFuture 相比 Future 有哪些优势

Future 就像打电话订外卖,打完电话就干等着,外卖到了才去拿。CompletableFuture 则是点完餐后手机能实时通知、还能组合多个订单、超时自动退款——智能得多。
// Future 的痛点:想等 A 完成后再执行 B,代码很难写
new Thread(() -> {
Integer a = future.get(); // 必须先阻塞等待
Integer b = computeB(a); // 再执行 B
}).start();
// CompletableFuture:链式调用,清晰简洁
CompletableFuture.supplyAsync(() -> computeA())
.thenApply(a -> computeB(a)) // A 完成后自动执行 B
.thenAccept(b -> System.out.println(b)); // 消费最终结果
Q2:runAsync 和 supplyAsync 的区别是什么?各自的适用场景?
// runAsync:无返回值,适合"发后不管"的场景
CompletableFuture<Void> logFuture = CompletableFuture.runAsync(() -> {
logService.saveLog(userId, action); // 不需要返回值
}, executor);
// supplyAsync:有返回值,适合需要获取结果的场景
CompletableFuture<UserInfo> userFuture = CompletableFuture.supplyAsync(() -> {
return userService.getUser(userId); // 返回用户信息
}, executor);
UserInfo user = userFuture.join(); // 获取结果
Q3:thenApply、thenAccept、thenRun 的使用场景分别是什么?

是否需要上游的结果?
├─ 是 → 是否需要返回新结果?
│ ├─ 是 → thenApply
│ └─ 否 → thenAccept
└─ 否 → thenRun

Q4:exceptionally 和 handle 的区别是什么?什么时候用哪个?
核心回答要点:
|
方法 |
触发时机 |
能否修改结果 |
类比 |
|---|---|---|---|
exceptionally |
仅异常时触发 |
能(返回默认值) |
保险理赔:出事了才赔 |
handle |
成功或异常都触发 |
能(统一处理) |
体检报告:无论好坏都出报告 |
whenComplete |
成功或异常都触发 |
不能(只观察) |
监控摄像头:只记录不干预 |
// exceptionally:只在异常时兜底
CompletableFuture<String> f1 = CompletableFuture
.<String>supplyAsync(() -> { throw new RuntimeException("失败"); })
.exceptionally(ex -> "默认值"); // 异常时返回默认值
// 正常流程不受影响
// handle:无论成功失败都处理
CompletableFuture<String> f2 = CompletableFuture
.supplyAsync(() -> "Success")
.handle((result, ex) -> {
if (ex != null) {
return "异常:" + ex.getMessage();
}
return "结果:" + result;
});
// 成功时 result="Success", ex=null
// 失败时 result=null, ex=异常
// whenComplete:只观察不修改
CompletableFuture<String> f3 = CompletableFuture
.supplyAsync(() -> "Hello")
.whenComplete((result, ex) -> {
log.info("完成,结果:{}", result); // 不能修改 result
});
// f3 的结果仍然是 "Hello"
选型建议:
-
• 只需要异常兜底 →
exceptionally -
• 需要统一处理成功和失败 →
handle -
• 只需要观察完成状态(如日志监控)→
whenComplete
Q5:如何组合多个 CompletableFuture?thenCombine、allOf、anyOf 各自的特点?
方法
等待条件
结果数量
返回值
thenCombine两个都完成
2 个
合并后的新结果
allOf所有都完成
N 个
CompletableFuture<Void>(需手动获取各结果)
anyOf任意一个完成
1 个
最先完成的那个结果
代码示例:
// thenCombine:两个独立任务都完成后合并
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> combined = f1.thenCombine(f2, (s1, s2) -> s1 + " " + s2);
// "Hello World"
// allOf:等待所有任务完成(适合批量场景)
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "A");
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "B");
CompletableFuture<String> f3 = CompletableFuture.supplyAsync(() -> "C");
CompletableFuture<Void> all = CompletableFuture.allOf(f1, f2, f3);
all.join(); // 等所有完成
String r1 = f1.join(); // 手动获取各结果
String r2 = f2.join();
String r3 = f3.join();
// anyOf:等最快的那个(适合冗余请求)
CompletableFuture<String> fast = CompletableFuture.supplyAsync(() -> "快的");
CompletableFuture<String> slow = CompletableFuture.supplyAsync(() -> {
Thread.sleep(1000);
return "慢的";
});
Object result = CompletableFuture.anyOf(fast, slow).join(); // "快的"实战场景:
•
thenCombine:两个数据源都查完后合并展示(如用户信息 + 订单信息)•
allOf:批量查询 100 个订单,等全部完成后统一处理•
anyOf:同时请求主备两个数据源,谁快用谁
Q6:thenApply 和 thenApplyAsync 的区别是什么?什么时候该用异步版本?
核心回答要点:
|
方法 |
执行线程 |
性能 |
适用场景 |
|---|---|---|---|
thenApply |
和上游任务同一个线程 |
高(无线程切换) |
大部分场景 |
thenApplyAsync |
新线程(默认 ForkJoinPool 或指定线程池) |
低(有线程切换开销) |
需要线程隔离、避免长任务阻塞 |
// thenApply:和上游同一个线程
String thread1 = CompletableFuture
.supplyAsync(() -> {
System.out.println("上游线程:" + Thread.currentThread().getName());
return "Hello";
})
.thenApply(s -> {
System.out.println("thenApply线程:" + Thread.currentThread().getName());
return s + " World";
}).join();
// 两个打印的线程名相同
// thenApplyAsync:使用新线程
String thread2 = CompletableFuture
.supplyAsync(() -> "Hello")
.thenApplyAsync(s -> {
System.out.println("异步线程:" + Thread.currentThread().getName());
return s + " World";
}, executor) // 可指定线程池
.join();
// 可能在不同的线程执行
选型建议:
-
• 默认使用
thenApply(无额外开销,适合大部分场景) -
• 下游任务耗时较长、需要与上游隔离时才用
thenApplyAsync -
• 如果 CPU 密集型任务,用
thenApply避免上下文切换;如果 IO 密集型,可以用thenApplyAsync释放上游线程
常见误区:最初以为带 Async 后缀的性能更好,实际上恰恰相反——线程切换有成本,能不用就不用。
Q7:如何为 CompletableFuture 设置超时?JDK8 和 JDK9+ 分别怎么做?
JDK9+ 原生支持:
// 超时抛出 TimeoutException
CompletableFuture<String> f1 = CompletableFuture
.supplyAsync(() -> callExternalService())
.orTimeout(5, TimeUnit.SECONDS);
// 超时返回默认值(不抛异常)
CompletableFuture<String> f2 = CompletableFuture
.supplyAsync(() -> callExternalService())
.completeOnTimeout("降级默认值", 5, TimeUnit.SECONDS);
JDK8 手动实现:
public static <T> CompletableFuture<T> timeout(
CompletableFuture<T> future, long timeout, TimeUnit unit) {
CompletableFuture<T> timeoutFuture = new CompletableFuture<>();
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
// 超时后标记为异常完成
scheduler.schedule(() -> {
timeoutFuture.completeExceptionally(new TimeoutException("超时"));
}, timeout, unit);
// 原始任务完成后覆盖超时
future.whenComplete((result, ex) -> {
if (ex != null) {
timeoutFuture.completeExceptionally(ex);
} else {
timeoutFuture.complete(result);
}
});
return timeoutFuture;
}
Q8:为什么建议 CompletableFuture 使用自定义线程池而不是默认的 ForkJoinPool?
核心回答要点:
默认线程池
ForkJoinPool.commonPool()存在以下问题:问题 1:全局共享,互相影响
ForkJoinPool 是所有未指定线程池的 CompletableFuture 共享的。如果一个业务模块的异步任务把线程池占满了,其他模块的异步任务也会被阻塞。
问题 2:线程数有限
ForkJoinPool 的默认并行度等于 CPU 核心数减 1。对于 IO 密集型任务来说远远不够。
问题 3:线程命名不规范,问题排查困难
ForkJoinPool 的线程名是
ForkJoinPool.commonPool-worker-N,出问题后很难定位是哪个业务模块。问题 4:拒绝策略不可控
共享线程池的拒绝策略是固定的,不同业务可能需要不同的策略
。
// ✅ 使用自定义线程池
ExecutorService orderExecutor = new ThreadPoolExecutor(
10, // 核心线程数
20, // 最大线程数
60L, TimeUnit.SECONDS, // 空闲超时
new ArrayBlockingQueue<>(100), // 有界队列
new ThreadFactory() {
private final AtomicInteger count = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("order-async-" + count.incrementAndGet());
return t;
}
},
new ThreadPoolExecutor.CallerRunsPolicy() // 队列满时由调用线程执行
);
CompletableFuture.supplyAsync(() -> orderService.query(), orderExecutor);
线程池参数配置原则:
|
任务类型 |
核心线程数 |
最大线程数 |
队列 |
|---|---|---|---|
|
CPU 密集 |
CPU 核心数 |
CPU 核心数 + 1 |
无界或有界 |
|
IO 密集 |
CPU 核心数 × 2 |
CPU 核心数 × 2 ~ 4 倍 |
有界(防止 OOM) |
|
混合型 |
分开两个池 |
分别配置 |
有界 |
Q9:能说出至少 3 个 CompletableFuture 的实战应用场景吗?
场景 1:多服务并行调用(电商详情页)
// 串行:300ms // 并行:约 100ms CompletableFuture<UserInfo> userF = CompletableFuture.supplyAsync( () -> userService.getUser(userId), executor); CompletableFuture<OrderInfo> orderF = CompletableFuture.supplyAsync( () -> orderService.getOrder(userId), executor); Result result = userF .thenCombine(orderF, (user, order) -> combine(user, order)) .get(200, TimeUnit.MILLISECONDS);
场景 2:异步日志记录(非阻塞)
// 主流程不等待日志写完
CompletableFuture.runAsync(() -> {
logService.save(userId, action);
}).exceptionally(ex -> {
// 日志失败不影响主流程
System.err.println("日志失败:" + ex.getMessage());
return null;
});
场景 3:批量数据处理(订单批量查询)
List<Long> orderIds = Arrays.asList(1L, 2L, 3L, 4L, 5L); List<CompletableFuture<Order>> futures = orderIds.stream() .map(id -> CompletableFuture.supplyAsync( () -> orderService.getById(id), executor)) .collect(Collectors.toList()); List<Order> orders = CompletableFuture.allOf( futures.toArray(new CompletableFuture[0])) .thenApply(v -> futures.stream() .map(CompletableFuture::join) .collect(Collectors.toList())) .join();
场景 4:链式处理订单(创建 → 支付 → 发货 → 通知)
CompletableFuture<Order> orderFuture = CompletableFuture
.supplyAsync(() -> orderService.create(order), executor)
.thenApply(order -> { paymentService.pay(order); return order; })
.thenApply(order -> { shipmentService.ship(order); return order; })
.thenAccept(order -> notificationService.notify(order))
.exceptionally(ex -> { log.error("失败", ex); return null; });
Q10:thenCompose 和 thenCombine 的区别是什么?
维度
thenCompose
thenCombine
任务关系
串行依赖(B 依赖 A 的结果)
并行独立(两个任务互不影响)
执行方式
等 A 完成后才启动 B
A 和 B 同时启动
参数
接收一个返回 CompletableFuture 的函数
接收另一个 CompletableFuture 和合并函数
类比
接力赛:等前一棒跑完下一棒才起跑
拔河:两队同时发力
代码对比:
// thenCompose:串行,B 依赖 A 的结果
// 场景:先查用户,再用用户ID查订单
CompletableFuture<Order> order = CompletableFuture
.supplyAsync(() -> userService.getUser(userId))
.thenCompose(user -> CompletableFuture.supplyAsync(
() -> orderService.getOrders(user.getId())));
// thenCombine:并行,两个独立任务
// 场景:同时查用户信息和订单信息,都查完后合并
CompletableFuture<DemoDetail> detail =
CompletableFuture.supplyAsync(() -> userService.getUser(userId))
.thenCombine(
CompletableFuture.supplyAsync(() -> orderService.getOrders(userId)),
(user, orders) -> new DemoDetail(user, orders));
更多推荐

所有评论(0)