【Java并发】CompletableFuture详解:常用API和底层原理
- 1、为什么要引入Future和CompletableFuture?
-
- [1.1 传统异步计算存在问题?](#1.1 传统异步计算存在问题?)
- [1.2 Future的引入](#1.2 Future的引入)
- [1.3 Future 的局限性](#1.3 Future 的局限性)
- [1.4 CompletableFuture的引入](#1.4 CompletableFuture的引入)
- [2、CompletableFuture常用 API 详解](#2、CompletableFuture常用 API 详解)
-
- [2.1 创建 CompletableFuture](#2.1 创建 CompletableFuture)
- [2.2 结果转换与消费](#2.2 结果转换与消费)
- [2.3 组合多个 CompletableFuture](#2.3 组合多个 CompletableFuture)
- [2.4 等待多个任务完成:allOf 与 anyOf](#2.4 等待多个任务完成:allOf 与 anyOf)
-
- [2.4.1 allOf:等待所有任务完成](#2.4.1 allOf:等待所有任务完成)
- [2.4.2 anyOf:任意一个任务完成](#2.4.2 anyOf:任意一个任务完成)
- [2.4.3 allOf 原理与异常处理](#2.4.3 allOf 原理与异常处理)
- [2.4.4 anyOf 原理与异常处理](#2.4.4 anyOf 原理与异常处理)
- 3、异常处理机制
-
- [3.1 异常的传播](#3.1 异常的传播)
- [3.2 处理异常的方法](#3.2 处理异常的方法)
- [3.3 异常处理的底层原理](#3.3 异常处理的底层原理)
- 4、completableFuture原理解析
-
- [4.1 类继承关系](#4.1 类继承关系)
- [4.2 核心字段](#4.2 核心字段)
- [4.3 核心方法源码解析](#4.3 核心方法源码解析)
-
- [4.3.1 创建与完成](#4.3.1 创建与完成)
- [4.3.2 thenApply 原理](#4.3.2 thenApply 原理)
-
- [4.3.2.1 case1--不同future执行thenApply方法](#4.3.2.1 case1--不同future执行thenApply方法)
- [4.3.2.2 case2--同一个future执行thenApply方法](#4.3.2.2 case2--同一个future执行thenApply方法)
- [4.3.3 组合方法:thenCombine 原理](#4.3.3 组合方法:thenCombine 原理)
- [4.4.4 与核心 API 的对比理解](#4.4.4 与核心 API 的对比理解)
1、为什么要引入Future和CompletableFuture?
1.1 传统异步计算存在问题?
传统异步计算有一个核心问题是:无法获取异步任务的返回值。
java
public class TraditionalAsyncDemo {
// 模拟一个耗时操作
public static String fetchUserInfo() {
try {
Thread.sleep(2000); // 模拟网络请求
} catch (InterruptedException e) {
e.printStackTrace();
}
return "UserInfo: {name='张三', age=25}";
}
// 传统方式:使用Thread + 回调
public static void traditionalWay() {
System.out.println("开始获取用户信息...");
// 启动新线程执行耗时任务
new Thread(() -> {
String result = fetchUserInfo();
System.out.println("获取结果: " + result);
}).start();
System.out.println("主线程继续执行其他任务...");
}
public static void main(String[] args) {
traditionalWay();
// 主线程不能等待结果,也无法获取返回值
}
}
运行结果:
java
开始获取用户信息...
主线程继续执行其他任务...
获取结果: UserInfo: {name='张三', age=25}
1.2 Future的引入
Future 接口提供了异步计算结果的占位符,允许我们在提交任务后继续执行其他操作,稍后再获取结果:
java
import java.util.concurrent.*;
public class FutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(2);
System.out.println("开始执行异步任务...");
// 提交异步任务,返回Future
Future<String> future = executor.submit(() -> {
Thread.sleep(2000); // 模拟耗时操作
return "Task result: Hello Future!";
});
System.out.println("主线程继续执行其他操作...");
// 其他操作...
Thread.sleep(1000);
// 获取异步结果(会阻塞直到结果可用)
System.out.println("开始获取结果...");
String result = future.get(); // 阻塞等待
System.out.println("获取到结果: " + result);
executor.shutdown();
}
}
运行结果:
java
开始执行异步任务...
主线程继续执行其他操作...
开始获取结果...
获取到结果: Task result: Hello Future!
1.3 Future 的局限性
尽管 Future 解决了返回值问题,但它仍然存在很多局限:
1、无法手动完成计算:不能主动设置结果。
2、不支持链式调用:无法将结果直接传递给下一个任务。
3、无法组合多个 Future:不能优雅地合并异步任务。
4、获取结果时必须阻塞:get() 方法会阻塞主线程。
5、异常处理复杂:需要先捕获 ExecutionException 才能拿到原始异常。
java
import java.util.concurrent.*;
public class FutureLimitationsDemo {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(3);
// 局限性1:无法手动完成
Future<String> future = executor.submit(() -> "task");
// future.complete(...); // 不存在这样的方法!无法提前设置结果
// 局限性2:无法链式调用
Future<String> f1 = executor.submit(() -> "hello");
// 无法将结果传递给下一个任务,比如 f1 -> toUpperCase -> 输出
// 局限性3:无法组合多个Future
Future<String> f2 = executor.submit(() -> "World");
// 无法优雅地组合 f1 和 f2 的结果
// 局限性4:获取结果时会阻塞
Future<String> blocking = executor.submit(() -> {
Thread.sleep(2000);
return "result";
});
String result = blocking.get(); // 必须阻塞等待,无法设置超时或不等待
// 局限性5:异常处理复杂
Future<String> errorFuture = executor.submit(() -> {
throw new RuntimeException("Task failed!");
});
try {
errorFuture.get();
} catch (ExecutionException e) {
// 需要捕获 ExecutionException 才能拿到原始异常
System.out.println("原始异常:" + e.getCause().getMessage());
}
executor.shutdown();
}
}
运行结果:
java
原始异常:Task failed!
1.4 CompletableFuture的引入
下面的代码完整演示了 CompletableFuture 如何解决 Future 的 5 大局限:
1、无法手动完成计算:可以使用complete方法手动设置结果。
2、不支持链式调用:可以使用thenApply、thenAccept、thenRun方法进行链式调用。
3、无法组合多个 Future:可以使用thenCombine等方法优雅地合并异步任务。
4、获取结果时必须阻塞:可以使用getNow方法立刻获取值,或者使用get(1, TimeUnit.SECONDS)设置超时时间。
5、异常处理复杂:可以使用exceptionally(ex -> {})、handle()方法处理异常情况。不需要先捕获 ExecutionException 才能拿到原始异常。
java
import java.util.concurrent.*;
import java.util.function.*;
public class CompletableFutureVsFutureDemo {
public static void main(String[] args) throws Exception {
// ========== 局限性1:无法手动完成 ==========
// Future 没有 complete() 方法,只能被动等待任务完成
// CompletableFuture 可以手动设置结果
CompletableFuture<String> manualFuture = new CompletableFuture<>();
// 模拟其他线程异步设置结果
new Thread(() -> {
try {
Thread.sleep(1000);
manualFuture.complete("手动设置的结果");
// manualFuture.completeExceptionally(new RuntimeException("手动异常"));
} catch (InterruptedException e) {
manualFuture.completeExceptionally(e);
}
}).start();
System.out.println("解决局限性1---手动完成结果: " + manualFuture.get()); // 输出: 手动设置的结果
// ========== 局限性2:无法链式调用 ==========
// Future 不能将计算结果直接传递给下一个任务
// CompletableFuture 支持 thenApply、thenAccept、thenRun 等链式操作
CompletableFuture.supplyAsync(() -> "hello")
.thenApply(s -> s.toUpperCase()) // 转换结果
.thenApply(s -> s + " WORLD") // 再次转换
.thenAccept(System.out::println) // 消费结果,无返回值
.thenRun(() -> System.out.println("\n解决局限性2---链式调用完成")); // 最终执行
// 输出: HELLO WORLD\n链式调用完成
// ========== 局限性3:无法组合多个Future ==========
// Future 无法优雅地组合多个异步任务的结果
// CompletableFuture 提供 thenCombine、allOf、anyOf 等组合方法
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> combined = f1.thenCombine(f2, (r1, r2) -> r1 + " " + r2);
System.out.println("\n解决局限性3---组合结果是: " + combined.get()); // 输出: Hello World
// ========== 局限性4:获取结果时会阻塞 ==========
// Future.get() 是阻塞的,无法设置超时或非阻塞获取
// CompletableFuture 提供非阻塞获取方法 getNow()
CompletableFuture<String> blockingFuture = CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(2000); } catch (InterruptedException e) {}
return "延迟结果";
});
// 非阻塞获取:若未完成则返回默认值
String now = blockingFuture.getNow("默认值");
System.out.println("\n解决局限性4---非阻塞获取: " + now); // 大概率输出: 默认值
// 也可以设置超时,超时未完成则抛出异常
try {
blockingFuture.get(1, TimeUnit.SECONDS);
} catch (TimeoutException e) {
System.out.println("超时了,还没拿到结果");
}
// ========== 局限性5:异常处理复杂 ==========
// Future 需要捕获 ExecutionException 才能拿到原始异常
// CompletableFuture 提供 exceptionally、handle 等便捷方法
CompletableFuture<String> errorFuture = CompletableFuture.<String>supplyAsync(() -> {
throw new RuntimeException("原始异常");
}).exceptionally(ex -> {
System.out.println("优雅处理异常: " + ex.getMessage());
return "降级结果";
});
System.out.println("\n解决局限性5---异常处理后的结果: " + errorFuture.get()); // 输出: 降级结果
Thread.sleep(2000); // 等待所有异步任务结束
}
}
运行结果:
java
解决局限性1---手动完成结果: 手动设置的结果
HELLO WORLD
解决局限性2---链式调用完成
解决局限性3---组合结果是: Hello World
解决局限性4---非阻塞获取: 默认值
超时了,还没拿到结果
优雅处理异常: java.lang.RuntimeException: 原始异常
解决局限性5---异常处理后的结果: 降级结果
2、CompletableFuture常用 API 详解
2.1 创建 CompletableFuture
1、直接new CompletableFuture<>();
2、runAsync:无返回值
3、supplyAsync:有返回值
4、指定线程池
java
import java.util.concurrent.*;
public class CreateDemo {
public static void main(String[] args) throws Exception {
// 1. 直接 new(手动完成)
CompletableFuture<String> future = new CompletableFuture<>();
future.complete("手动完成");
System.out.println(future.get()); // 输出: 手动完成
// 2. runAsync:无返回值
CompletableFuture<Void> runFuture = CompletableFuture.runAsync(() -> {
System.out.println("runAsync 执行,无返回值");
});
runFuture.get(); // 等待完成
// 3. supplyAsync:有返回值
CompletableFuture<String> supplyFuture = CompletableFuture.supplyAsync(() -> {
return "supplyAsync 执行,有返回值";
});
System.out.println(supplyFuture.get());
// 4. 指定线程池
ExecutorService executor = Executors.newFixedThreadPool(2);
CompletableFuture<String> customFuture = CompletableFuture.supplyAsync(() -> {
return "自定义线程池: " + Thread.currentThread().getName();
}, executor);
System.out.println(customFuture.get());
executor.shutdown();
}
}
运行结果:
java
手动完成
runAsync 执行,无返回值
supplyAsync 执行,有返回值
自定义线程池: pool-1-thread-1
2.2 结果转换与消费
一、根据入参和出参来分类:
1、thenApply(Function):有入参、有返回值。
2、thenAccept(Consumer):有入参,无返回值。
3、thenRun(Runnable):无入参,无返回值。
二、根据与上游是否共用线程来区分
1、thenApply(Function):同步执行转换,与上游共用线程(若上游已完成,则在当前线程执行)。
2、thenApplyAsync(Function, Executor):异步执行转换,使用自定义线程池,不使用上游的线程。
三、根据返回的CompletableFuture是否扁平化来区分
1、thenApply:返回的是非嵌套的future,CompletableFuture<CompletableFuture>
2、thenCompose:返回的是扁平化的future,CompletableFuture
java
import java.util.concurrent.*;
public class TransformDemo {
public static void main(String[] args) throws Exception {
// 有入参有返回值:将结果转为大写,再拼接后缀
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "hello")
.thenApply(s -> s.toUpperCase()) // String -> String
.thenApply(s -> s + " WORLD"); // 链式调用,继续转换
System.out.println("thenApply--有入参、有返回值: " + f1.get()); // HELLO WORLD
// 异步转换(切换线程)
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "hello")
.thenApplyAsync(s -> {
System.out.println(" 异步转换线程: " + Thread.currentThread().getName());
return s.toUpperCase();
});
System.out.println(" thenApplyAsync: " + f2.get()); // HELLO
// 有入参无返回值:消费结果并打印
CompletableFuture<Void> f3 = CompletableFuture.supplyAsync(() -> "hello")
.thenAccept(s -> System.out.println("thenAccept--有入参、无返回值---消费结果: " + s));
f3.get(); // 等待消费完成
// 无入参无返回值:上游完成后执行一段逻辑
CompletableFuture<Void> f4 = CompletableFuture.supplyAsync(() -> "hello")
.thenRun(() -> System.out.println("thenRun--无入参、无返回值--上游任务执行完毕"));
f4.get();
// 嵌套情况(不推荐)
System.out.println("\n");
CompletableFuture<CompletableFuture<String>> nestedFuture =
CompletableFuture.supplyAsync(() -> "hello")
.thenApply(s -> CompletableFuture.supplyAsync(() -> s + " world"));
// 使用 thenCompose 扁平化
CompletableFuture<String> flatFuture = CompletableFuture.supplyAsync(() -> "hello")
.thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " world"));
System.out.println("thenCompose: " + flatFuture.get()); // hello world
}
}
运行结果:
java
thenApply--有入参、有返回值: HELLO WORLD
异步转换线程: ForkJoinPool.commonPool-worker-1
thenApplyAsync: HELLO
thenAccept--有入参、无返回值---消费结果: hello
thenRun--无入参、无返回值--上游任务执行完毕
thenCompose: hello world
2.3 组合多个 CompletableFuture
1、thenCombine:合并2个future的结果,可以有返回值。
2、thenAcceptBoth:合并2个future的结果,无返回值。
3、applyToEither:取先完成的结果
java
import java.util.concurrent.*;
public class CombineDemo {
public static void main(String[] args) throws Exception {
// 1. thenCombine:合并两个独立结果
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> combined = f1.thenCombine(f2, (r1, r2) -> r1 + " " + r2);
System.out.println("thenCombine: " + combined.get());
// 2. thenAcceptBoth:组合并消费
f1.thenAcceptBoth(f2, (r1, r2) -> System.out.println("thenAcceptBoth: " + r1 + " " + r2))
.get();
// 3. applyToEither:取先完成的结果
CompletableFuture<String> fast = CompletableFuture.supplyAsync(() -> {
sleep(1000);
return "Fast";
});
CompletableFuture<String> slow = CompletableFuture.supplyAsync(() -> {
sleep(2000);
return "Slow";
});
CompletableFuture<String> either = fast.applyToEither(slow, s -> s + " wins!");
System.out.println("applyToEither: " + either.get());
}
static void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) {}
}
}
运行结果:
java
thenCombine: Hello World
thenAcceptBoth: Hello World
applyToEither: Fast wins!
2.4 等待多个任务完成:allOf 与 anyOf
2.4.1 allOf:等待所有任务完成
java
import java.util.concurrent.*;
import java.util.stream.*;
public class AllOfDemo {
public static void main(String[] args) throws Exception {
// 创建 3 个任务
CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
sleep(1000);
return "Task1";
});
CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
sleep(2000);
return "Task2";
});
CompletableFuture<String> task3 = CompletableFuture.supplyAsync(() -> {
sleep(1500);
return "Task3";
});
// allOf 返回 CompletableFuture<Void>,等待所有任务完成
CompletableFuture<Void> all = CompletableFuture.allOf(task1, task2, task3);
// 阻塞等待所有任务完成
all.get();
// 所有任务完成后,收集结果
String result = Stream.of(task1, task2, task3)
.map(CompletableFuture::join) // join 不抛出 checked exception
.collect(Collectors.joining(", "));
System.out.println("所有任务完成: " + result);
}
static void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) {}
}
}
运行结果:
java
所有任务完成: Task1, Task2, Task3
2.4.2 anyOf:任意一个任务完成
java
import java.util.concurrent.*;
public class AnyOfDemo {
public static void main(String[] args) throws Exception {
CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
sleep(2000);
return "Task1 (slow)";
});
CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
sleep(1000);
return "Task2 (fast)";
});
// anyOf 返回 CompletableFuture<Object>
CompletableFuture<Object> any = CompletableFuture.anyOf(task1, task2);
// 获取最先完成的结果
Object result = any.get();
System.out.println("最先完成的任务: " + result);
// 注意:anyOf 完成后,其他任务依然在后台继续执行
Thread.sleep(2000); // 等待其他任务完成,避免程序退出
}
static void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) {}
}
}
演示结果:
java
最先完成的任务: Task2 (fast)
2.4.3 allOf 原理与异常处理
一、源码解析及注意事项
1、每个子任务完成(正常或异常)时,都会减少计数器。
2、如果子任务异常,会立即调用 result.completeExceptionally(t),即 result 会马上被标记为异常完成(即使还有其他子任务未完成)。
3、但计数器仍然会继续递减,等待所有子任务完成(因为 whenComplete 回调始终会执行)。
4、当所有子任务都完成后,如果之前没有异常,result 被正常完成;如果已有异常,则 result 已经完成,不再重复。
总结:
1、allOf 返回的 Future 在任一子任务异常时就会立即异常完成。
2、未完成的其他子任务不会被取消,会继续执行(它们的 whenComplete 回调仍会触发,但不再影响 result 的状态)。
3、异常是第一个发生的异常(通过 ex[0] 记录)。
java
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) {
CompletableFuture<Void> result = new CompletableFuture<>();
final AtomicInteger count = new AtomicInteger(cfs.length);
final Throwable[] ex = new Throwable[1]; // 用于记录第一个异常
for (CompletableFuture<?> cf : cfs) {
cf.whenComplete((r, t) -> {
// 1. 如果当前子任务异常,记录第一个异常并尝试异常完成 result
if (t != null) {
synchronized (ex) {
if (ex[0] == null) ex[0] = t;
}
result.completeExceptionally(t);
}
// 2. 计数减一
if (count.decrementAndGet() == 0) {
// 3. 当所有子任务都完成时,若没有异常则正常完成 result
if (ex[0] == null) {
result.complete(null);
}
// 如果有异常,result 已经异常完成,这里无需再处理
}
});
}
return result;
}
二、代码演示:allOf 的异常场景
1、allOf 在 task2 异常时立即完成并抛出异常。
2、其他任务(task1 和 task3)仍然正常执行完毕。
3、单独获取每个子任务的结果时,task2 抛出异常,其余正常。
java
import java.util.concurrent.*;
public class AllOfExceptionDemo {
public static void main(String[] args) {
// 创建三个任务:两个正常,一个异常
CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
sleep(500);
return "Task1 OK";
});
CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
sleep(1000);
throw new RuntimeException("Task2 失败了!");
});
CompletableFuture<String> task3 = CompletableFuture.supplyAsync(() -> {
sleep(1500);
return "Task3 OK";
});
// 等待所有任务完成(allOf 会在 task2 异常时立即异常)
CompletableFuture<Void> all = CompletableFuture.allOf(task1, task2, task3);
try {
all.join(); // 会抛出 CompletionException,包装 task2 的异常
} catch (CompletionException e) {
System.out.println("allOf 捕获异常: " + e.getCause().getMessage());
}
// 检查每个子任务的状态(注意 task1 和 task3 仍然会完成)
System.out.println("\n检查各子任务结果:");
System.out.println("task1 结果: " + task1.join());
try {
task2.join();
} catch (CompletionException e) {
System.out.println("task2 异常: " + e.getCause().getMessage());
}
System.out.println("task3 结果: " + task3.join());
}
static void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) {}
}
}
运行结果:
java
allOf 捕获异常: Task2 失败了!
检查各子任务结果:
task1 结果: Task1 OK
task2 异常: Task2 失败了!
task3 结果: Task3 OK
三、常见处理模式
java
// 等待所有任务完成(无论是否异常),然后收集结果
CompletableFuture.allOf(task1, task2, task3)
.exceptionally(ex -> { // 这里捕获的是 allOf 的异常,但不会取消其他任务
System.out.println("allOf 异常: " + ex.getMessage());
return null;
})
.join();
// 单独获取每个任务的结果(处理异常)
List<String> results = Stream.of(task1, task2, task3)
.map(f -> {
try {
return f.join();
} catch (CompletionException e) {
return "ERROR: " + e.getCause().getMessage();
}
})
.collect(Collectors.toList());
System.out.println(results);
2.4.4 anyOf 原理与异常处理
一、源码解析及注意事项
1、第一个完成(无论正常还是异常)的子任务,会立即完成 result。
2、如果先完成的是正常任务,result 正常完成;如果先完成的是异常任务,result 异常完成。
3、后续完成的任务不会影响 result 的状态。
4、未完成的任务不会被取消,它们会继续执行(可能导致资源浪费)。
java
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) {
CompletableFuture<Object> result = new CompletableFuture<>();
for (CompletableFuture<?> cf : cfs) {
cf.whenComplete((r, t) -> {
// 使用 CAS 保证只完成一次(内部实现是 complete 或 completeExceptionally)
if (t != null) {
result.completeExceptionally(t);
} else {
result.complete(r);
}
});
}
return result;
}
二、代码演示:anyOf 的异常场景
1、anyOf 的结果由最先完成的子任务决定(异常时抛出)。
2、未完成的任务(slowSuccess)仍会继续执行。
java
import java.util.concurrent.*;
public class AnyOfExceptionDemo {
public static void main(String[] args) {
// 创建两个任务:一个快但异常,一个慢但正常
CompletableFuture<String> fastFail = CompletableFuture.supplyAsync(() -> {
sleep(500);
throw new RuntimeException("快速失败任务异常");
});
CompletableFuture<String> slowSuccess = CompletableFuture.supplyAsync(() -> {
sleep(2000);
return "慢速成功任务结果";
});
CompletableFuture<Object> any = CompletableFuture.anyOf(fastFail, slowSuccess);
try {
Object result = any.get(); // 会立即抛出异常(因为 fastFail 先完成且异常)
System.out.println("anyOf 结果: " + result);
} catch (ExecutionException e) {
System.out.println("anyOf 捕获ExecutionException异常: " + e.getCause().getMessage());
} catch (InterruptedException e) {
System.out.println("anyOf 捕获InterruptedException异常: " + e.getCause().getMessage());
}
// 稍等,观察慢速任务是否仍然执行(不会取消)
sleep(3000);
System.out.println("慢速任务结果: " + slowSuccess.join());
}
static void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) {}
}
}
运行结果:
java
anyOf 捕获ExecutionException异常: 快速失败任务异常
慢速任务结果: 慢速成功任务结果
3、异常处理机制
3.1 异常的传播
在 CompletableFuture 中,异常会沿着调用链向下传播,直到被处理。
java
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class ExceptionPropagationDemo {
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("原始异常");
}).thenApply(s -> {
System.out.println("这个不会执行");
return s + " after";
}).thenApply(s -> {
System.out.println("这个也不会执行");
return s + " again";
});
try {
future.get();
} catch (ExecutionException e) {
System.out.println("捕获异常: " + e.getCause().getMessage());
}
}
}
运行结果:
java
捕获异常: 原始异常
3.2 处理异常的方法
一、exceptionally:从异常中恢复
exceptionally 只处理异常,如果上游正常完成,它不会执行。可以在exceptionally方法中处理异常并返回默认值。
java
import java.util.concurrent.CompletableFuture;
public class ExceptionallyDemo {
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.<String>supplyAsync(() -> {
throw new RuntimeException("任务失败");
}).exceptionally(ex -> {
System.out.println("异常处理: " + ex.getMessage());
return "默认值";
});
System.out.println("结果: " + future.get());
}
}
运行结果:
java
异常处理: java.lang.RuntimeException: 任务失败
结果: 默认值
二、handle:处理正常和异常结果
java
public class HandleDemo {
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 模拟正常或异常
if (Math.random() > 0.5) {
return "成功";
} else {
throw new RuntimeException("失败");
}
}).handle((result, ex) -> {
if (ex != null) {
return "异常处理: " + ex.getMessage();
}
return "正常结果: " + result;
});
System.out.println(future.get());
}
}
运行结果:
java
异常处理: java.lang.RuntimeException: 失败
三:whenComplete:不影响结果future的结果
java
public class WhenCompleteDemo {
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.<String>supplyAsync(() -> {
throw new RuntimeException("任务失败");
}).whenComplete((result, ex) -> {
if (ex != null) {
System.out.println("whenComplete方法--记录日志: " + ex.getMessage());
} else {
System.out.println("whenComplete方法--记录日志: 成功 " + result);
}
});
try {
future.get();
} catch (ExecutionException e) {
System.out.println("主流程捕获异常: " + e.getCause().getMessage());
}
}
}
运行结果:
java
whenComplete方法--记录日志: java.lang.RuntimeException: 任务失败
主流程捕获异常: 任务失败
3.3 异常处理的底层原理
CompletableFuture 内部使用一个 volatile Object result 来存储结果或异常。当任务执行异常时,会调用 completeExceptionally(Throwable ex),将 ex 包装成 AltResult 对象存入 result 字段。
java
// 异常完成的核心代码(简化)
public boolean completeExceptionally(Throwable ex) {
// 将异常包装为 AltResult,存入 result
return completeValue(new AltResult(ex));
}
static final class AltResult {
final Throwable ex;
AltResult(Throwable ex) { this.ex = ex; }
}
后续的依赖任务在触发时,会检查当前 Future 的 result 是否为 AltResult,如果是,则将异常传递下去,或调用异常处理方法。
以 thenApply 为例,内部实现会判断上游结果是否为异常。异常处理的链式传播正是通过这种机制实现的。
java
// thenApply 内部执行逻辑(简化)
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn) {
CompletableFuture<U> d = new CompletableFuture<>();
// 如果当前已经完成,直接执行
Object r = result;
if (r != null) {
// 如果结果异常,传播异常
if (r instanceof AltResult) {
d.completeExceptionally(((AltResult)r).ex);
} else {
// 正常执行转换
try {
d.complete(fn.apply((T) r));
} catch (Throwable ex) {
d.completeExceptionally(ex);
}
}
} else {
// 否则,将依赖任务加入栈中,等待完成时触发
push(new UniApply<>(d, fn));
}
return d;
}
4、completableFuture原理解析
4.1 类继承关系
CompletableFuture 实现了两个关键接口:
1、Future:提供异步任务的基本操作,如 get()、cancel()、isDone()。
2、CompletionStage:这是 Java 8 新增的接口,定义了异步任务编排的方法,如 thenApply、thenCombine、whenComplete 等。CompletableFuture 是它的核心实现。通过 CompletionStage 接口,我们才能像搭积木一样链式组合任务。
java
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
// ...
}
4.2 核心字段
CompletableFuture 的核心状态只有两个 volatile 字段(源码中还有 UNSAFE 辅助偏移量,但核心是这两个):
1、result 的取值:null(未完成)、T(正常结果)、AltResult(包装的异常)。
2、stack 是一个 Completion 节点,是等待任务的栈顶,是一个单向链表。每个节点代表一个等待此 Future 完成后执行的动作(如 thenApply 创建的 UniApply 节点)。
java
public class CompletableFuture<T> {
// 存储结果或异常。null 表示未完成,非 null 表示已完成(可能为 AltResult 包装异常)
volatile Object result;
// 等待任务的栈顶,是一个单向链表,存储所有依赖此 Future 完成的任务节点
volatile Completion stack;
// 其他辅助字段(如 UNSAFE 用于 CAS 操作)这里略去
}
Completion 是抽象类,它的子类对应各种操作,常用子类:
1、UniApply<T,V>:对应 thenApply
2、UniAccept:对应 thenAccept
3、BiApply<T,U,V>:对应 thenCombine
4、OrApply<T,U,V>:对应 applyToEither
每个子类都持有下游 CompletableFuture 和具体的函数,以及可能的上游结果。
java
abstract static class Completion extends ForkJoinTask<Void> {
volatile Completion next; // 链表的下一个节点
// 核心执行方法,由子类实现
abstract void run();
}
核心字段的使用方式:
- 状态存储:通过 volatile Object result 存储结果或异常,使用 CAS 保证原子更新。
- 依赖管理:每个 CompletableFuture 维护一个 stack 链表,存放所有等待它完成的任务节点
- 任务触发。当 complete 或 completeExceptionally 被调用时,postComplete 遍历链表并执行每个节点的 run 方法。
- 节点类型:每个编排方法(如 thenApply)都会生成一个对应的 Completion 子类,它持有下游 Future 和具体函数。
- 异常传播:若上游结果包含 AltResult,下游在 run 时会直接 completeExceptionally,异常沿链传播。
- 异步与同步:thenApply 与 thenApplyAsync 的区别在于:thenApply 在任务完成时由触发线程执行;thenApplyAsync 则总是提交到线程池。
4.3 核心方法源码解析
4.3.1 创建与完成
supplyAsync 简化逻辑:
1、正常完成执行complete方法,异常完成执行completeExceptionally方法。
2、异常完成会创建一个AltResult对象,并赋值给result字段。
3、result字段赋值完成后,执行postComplete方法,触发所有等待任务进行执行。即栈顶stack指向下一个Completion。
java
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
CompletableFuture<U> f = new CompletableFuture<>();
// 提交任务到默认线程池
ForkJoinPool.commonPool().execute(() -> {
try {
U result = supplier.get();
f.complete(result); // 正常完成
} catch (Throwable t) {
f.completeExceptionally(t); // 异常完成
}
});
return f;
}
// 正常完成
public boolean complete(T value) {
return completeValue(value);
}
// 异常完成
public boolean completeExceptionally(Throwable ex) {
return completeValue(new AltResult(ex));
}
// 底层完成方法(简化)
final boolean completeValue(Object r) {
// CAS 设置 result,从 null 变为 r(或 AltResult)
if (UNSAFE.compareAndSwapObject(this, RESULT_OFFSET, null, r)) {
// 设置成功后,触发所有等待任务
postComplete();
return true;
}
return false;
}
final void postComplete() {
CompletableFuture<?> f = this;
Completion h;
// 循环弹出栈顶
while ((h = f.stack) != null) {
// 弹出栈顶(CAS 将 stack 指向 h.next)
if (f.stack == h && UNSAFE.compareAndSwapObject(f, STACK_OFFSET, h, h.next)) {
// 执行任务
h.run();
// 注意:h.run() 可能会完成下游 Future,从而递归触发更下层的依赖
}
}
}
4.3.2 thenApply 原理
thenApply 方法会创建一个新的 CompletableFuture(下游)和一个 UniApply 节点,并将该节点推入上游的 stack 中(如果上游还未完成)。如果上游已经完成,则直接在当前线程执行转换。
java
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn) {
CompletableFuture<U> d = new CompletableFuture<>();
// 如果上游已完成,立即执行转换
Object r = result;
if (r != null) {
if (r instanceof AltResult) {
// 上游异常,传播异常
d.completeExceptionally(((AltResult)r).ex);
} else {
try {
// 正常转换
d.complete(fn.apply((T) r));
} catch (Throwable ex) {
d.completeExceptionally(ex);
}
}
} else {
// 上游未完成,将节点压入栈中
push(new UniApply<>(d, fn));
}
return d;
}
// 压栈操作(简化)
final void push(Completion c) {
do {
c.next = stack;
} while (!UNSAFE.compareAndSwapObject(this, STACK_OFFSET, c.next, c));
}
static final class UniApply<T,V> extends Completion {
CompletableFuture<V> dep; // 下游 Future
Function<? super T,? extends V> fn;
// 当上游完成时,会调用 run()
public void run() {
CompletableFuture<T> src = ...; // 上游
Object r = src.result;
// 如果上游结果异常,下游也异常
if (r instanceof AltResult) {
dep.completeExceptionally(((AltResult)r).ex);
} else {
try {
V v = fn.apply((T) r);
dep.complete(v);
} catch (Throwable ex) {
dep.completeExceptionally(ex);
}
}
}
}
4.3.2.1 case1--不同future执行thenApply方法
不同future执行thenApply方法,会按照thenApply的顺序依次执行。
1、stage_1执行的时候,会新建一个futureA,然后执行push方法。由于stage_1.stack一开始是null,所以先执行futureA.next=null,然后将stage_1.stack指向futureA。所以stage_1的stack的单向链表是:stage_1.stack-->futureA。
2、同理futureA的stack的单向链表是:futureA.stack-->futureB。
因此stage_1执行完后,会执行栈的下一个futureA。futureA执行完之后,会执行futureB。
java
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class OrderGuaranteeDemo2 {
public static void main(String[] args) {
// 创建一个单线程的线程池,保证任务按提交顺序依次执行
ExecutorService singleExecutor = Executors.newSingleThreadExecutor();
CompletableFuture<String> stage_1 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "hello";
});
CompletableFuture<String> futureA = stage_1.thenApplyAsync(s -> {
System.out.println("A 开始执行");
return s + "A";
}, singleExecutor);
CompletableFuture<String> futureB = futureA.thenApplyAsync(s -> {
System.out.println("B 开始执行");
return s + "B";
}, singleExecutor);
// 等待所有任务完成,确保程序不会提前退出
CompletableFuture.allOf(futureA, futureB).join();
// 关闭线程池
singleExecutor.shutdown();
}
}
运行结果:
java
A 开始执行
B 开始执行
4.3.2.2 case2--同一个future执行thenApply方法
同一个future执行thenApply方法,后执行thenApply的future会先抛出到线程池中,如果和下面的case一样,是单线程的话,一定可以看到先执行B,再执行A。但是如果不是单线程的线程池的话,则不能保证一定先执行B,因为线程的调度是由cpu决定的,从stack的代码层面来说,只是先抛出futureB,再抛出futureA。
1、stage_1执行的时候,会新建一个futureA,然后执行push方法。由于stage_1.stack一开始是null,所以先执行futureA.next=null,然后将stage_1.stack指向futureA。所以stage_1的stack的单向链表是:stage_1.stack-->futureA。
2、然后会新建futureB,然后执行push方法。先执行futureB.next=futureA,然后将stage_1.stack指向futureB。所以stage_1的stack的单向链表是:stage_1.stack-->futureB-->futureA。
3、stage_1执行完成后,会先将futureB抛出到线程池中进行执行,然后再将futureA抛出到线程池中执行。但是具体是谁先执行,这个要由cpu的调度来决定。
java
import java.util.concurrent.*;
public class OrderGuaranteeDemo {
public static void main(String[] args) throws Exception {
// 创建一个单线程的线程池,保证任务按提交顺序依次执行
ExecutorService singleExecutor = Executors.newSingleThreadExecutor();
// 第一个阶段:异步返回 "hello"
CompletableFuture<String> stage_1 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "hello";
});
// 先注册的任务 A(理论上应后被弹出)
CompletableFuture<String> futureA = stage_1.thenApplyAsync(s -> {
System.out.println("A 开始执行");
return s + "A";
}, singleExecutor);
// 后注册的任务 B(理论上应先被弹出并提交)
CompletableFuture<String> futureB = stage_1.thenApplyAsync(s -> {
System.out.println("B 开始执行");
return s + "B";
}, singleExecutor);
// 等待所有任务完成,确保程序不会提前退出
CompletableFuture.allOf(futureA, futureB).join();
// 关闭线程池
singleExecutor.shutdown();
}
}
4.3.3 组合方法:thenCombine 原理
thenCombine 需要等待两个上游都完成。它创建 BiApply 节点,节点中持有两个上游 Future 和下游 Future。当任意一个上游完成时,都会尝试检查另一个是否也完成,如果都完成,则执行合并函数。
java
public <U,V> CompletableFuture<V> thenCombine(
CompletionStage<? extends U> other,
BiFunction<? super T,? super U,? extends V> fn) {
CompletableFuture<V> d = new CompletableFuture<>();
// 创建 BiApply 节点,它会等待两个上游
BiApply<T,U,V> node = new BiApply<>(d, fn);
// 将自己注册到两个上游的栈中
push(node);
other.thenAcceptBoth(...); // 类似注册
return d;
}
4.4.4 与核心 API 的对比理解
- thenApply:创建 UniApply 节点,推入上游 stack 同步执行转换,可能由上游完成线程执行。
- thenApplyAsync:创建 UniApply 节点,但节点内部将任务提交到线程池,总是异步执行转换。
- thenCombine:创建 BiApply 节点,同时注册到两个上游,等待两个上游都完成后执行合并。
- allOf:创建一个新的 CompletableFuture,内部用计数器等待所有子任务完成,任一异常则立即完成异常。
- anyOf:创建新的 CompletableFuture,任意子任务完成时立即完成,结果由最先完成的子任务决定。