一、Future:java.util.concurrent.Future vs CompletableFuture
1. 线程池 ExecutorService.submit() 返回的传统 Future 为什么能中断线程?
传统 FutureTask(线程池底层实现类)持有任务与执行线程的引用 ,这是和 CompletableFuture 最核心的区别:
- 当线程池把任务丢给工作线程执行时,工作线程会绑定当前
FutureTask对象; FutureTask内部有成员变量runner,用来记录正在执行该任务的线程;- 调用
future.cancel(true)时,底层逻辑:- 判断任务是否正在运行;
- 如果
runner不为 null,直接调用runner.interrupt(),给目标线程发送中断标记; - 再把任务状态置为「取消」。
核心:传统 FutureTask 有 runner 字段存执行线程引用
源码简化示意:
java
public class FutureTask<T> implements RunnableFuture<T> {
private volatile Thread runner; // 正在执行此任务的线程引用
public boolean cancel(boolean mayInterruptIfRunning) {
if (mayInterruptIfRunning) {
Thread r = runner;
if (r != null)
r.interrupt(); // 直接中断执行线程
}
return true;
}
}
所以传统 Future 不是"凭空中断",它保存了执行任务的线程引用 ,才有能力调用 interrupt()。
2. CompletableFuture 为什么不保存执行线程?
CompletableFuture 的设计定位是通用结果容器(Promise),不绑定任何执行线程:
- 手动
new CompletableFuture<>()场景:没有固定任务线程,任意线程都能调用complete(),根本不存在唯一执行线程; supplyAsync/runAsync场景:只是把任务交给线程池执行,CompletableFuture不会记录、持有执行这个任务的线程;- 多任务竞速场景(你之前书中双任务示例):多个线程都能尝试
complete,没有单一归属线程。
它内部没有类似 runner 的线程引用字段 ,调用 cancel() 时,找不到任何线程去执行 interrupt(),只能单纯修改自身状态,标记为 CancellationException。
同时 cancel(boolean mayInterruptIfRunning) 的布尔参数完全失效,传 true/false 行为一模一样。
二、一句话对比两者 cancel 完整逻辑
1. 传统 Future(FutureTask)
- 持有执行线程引用
runner; cancel(true):- 修改任务状态为取消;
- 拿到
runner线程,调用thread.interrupt(); - 后续阻塞
get()抛出CancellationException;
cancel(false):只改状态,不发中断,等任务自然跑完。
2. CompletableFuture
- 不持有任何执行线程引用;
cancel(任意布尔值):- 仅修改自身状态为异常完成,内部存入
CancellationException; - 不会执行任何线程中断操作,后台任务会继续跑完;
- 后续
get/join/回调都会感知到取消异常。
- 仅修改自身状态为异常完成,内部存入
三、延伸:如果需要真正中断 CompletableFuture 的任务该怎么做?
既然 cancel() 没用,只能业务侧自行控制:
-
方案1:线程中断标记判断
任务内循环读取Thread.currentThread().isInterrupted(),外部手动拿到线程调用interrupt()(不能靠f.cancel()); -
方案2:自定义 volatile 开关标记
javavolatile boolean stop = false; CompletableFuture.runAsync(() -> { while (!stop) { ...业务逻辑... } }); // 外部终止任务 stop = true; -
方案3:超时自动放弃
orTimeout()java// 3秒未完成则抛出超时异常,仅改变Future状态,不会停止任务 f.orTimeout(3, TimeUnit.SECONDS); -
替代方案:改用线程池
FutureTask
如果需求是"随时可中断运行中任务",放弃纯CompletableFuture,用executor.submit(Callable)获取传统 Future。
一、Java CompletableFuture ≈ JS Promise
二者底层设计思想几乎同源,都是异步可外部决议的模型,写法、能力一一对应:
1. 核心概念对照表
| Java CompletableFuture | JavaScript Promise | 作用 |
|---|---|---|
new CompletableFuture<T>() |
new Promise((resolve, reject) => {}) |
手动创建可外部完成的异步容器 |
f.complete(val) |
resolve(val) |
成功决议,填入正常结果 |
f.completeExceptionally(ex) |
reject(err) |
异常决议,抛出错误 |
thenAccept(r -> {}) |
.then(res => {}) |
消费结果,无返回值 |
thenApply(r -> newVal) |
.then(res => transformRes) |
转换结果,生成新异步对象 |
whenComplete((res, err) -> {}) |
.finally() |
无论成功失败都会执行收尾 |
exceptionally(ex -> fallback) |
.catch(err => fallback) |
捕获异常,返回降级默认值 |
CompletableFuture.anyOf() |
Promise.race() |
多个异步,取最先完成的那个 |
CompletableFuture.allOf() |
Promise.all() |
等待所有异步全部完成 |
双任务竞速取最快结果 的案例,完全等价 JS 的 Promise.race,逻辑一模一样。
2. 编码范式一致:订阅优先
JS 标准写法:先注册 .then() / .catch(),再执行 resolve/reject
javascript
// JS
const p = new Promise((resolve) => {
setTimeout(() => resolve(100), 500);
});
// 先注册回调
p.then(res => console.log(res));
Java CompletableFuture 标准写法:先 thenAccept 注册回调,再子线程调用 complete
java
// Java
var f = new CompletableFuture<Integer>();
// 先注册回调
f.thenAccept(res -> System.out.println(res));
new Thread(() -> {
Thread.sleep(500);
f.complete(100);
}).start();
3. 阻塞 vs 异步等待对应
- Java
f.get()/f.join()≈ JS 顶层await f
二者都是同步阻塞等待异步结果 ;
走到等待代码时,如果异步已经完成,直接返回,无阻塞。
javascript
// JS await 等价 Java join/get
async function test() {
const p = new Promise(resolve => setTimeout(() => resolve(100), 500));
// 先执行一堆耗时逻辑
await new Promise(r => setTimeout(r, 1000));
// 任务早就完成,await 直接拿到结果不等待
const res = await p;
}
二、关键差异(别踩坑)
虽然用法像,但语言底层模型有本质区别:
- 线程模型完全不同
- JS:单线程事件循环,异步只是回调排队,不存在多线程并发竞争;
- Java:多线程操作系统内核线程,
complete()存在多线程竞态,需要考虑线程安全。
- 回调线程可控性
- JS:回调永远在主线程事件循环执行;
- Java:
thenAccept在执行complete的线程运行;thenAcceptAsync可自定义线程池,灵活控制回调执行线程。
- 异常机制区分
- JS:
reject后必须.catch,否则全局 unhandledRejection 报错; - Java:未捕获异常只会存放在 Future 内部,不主动抛出,只有调用
get/join才会触发异常。
- JS:
- 生命周期控制
- Java 可以重复调用
complete(只会第一次生效);JS 的resolve/reject调用多次无任何效果,行为一致。
- Java 可以重复调用
三、补充:为什么 Java 8 要借鉴 Promise 设计 CompletableFuture
在 Java 8 之前普通 Future 是残缺的:
只能阻塞等待,无法链式回调、不能外部手动完成,相当于只有 JS Promise 一半功能。
CompletableFuture 直接借鉴了主流异步模型 Promise 的设计思路,补上了异步编排、手动决议、多任务组合的短板,所以写起来语法和思维和 JS 异步高度趋同。
如果你平时写前端 JS,上手 CompletableFuture 会非常顺滑,唯一需要额外注意的就是 Java 多线程并发安全问题。