第11章 《CompletableFuture:组合式异步编程》
1、核心概念
Future的局限性:
无法手动设置完成结果
- 一旦Future任务开始执行,无法从外部手动设置其结果
- 如果任务卡住或需要提前返回,无法干预
public static void setResultTest() throws ExecutionException, InterruptedException {
// Future - 无法手动完成
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<String> future = executorService.submit(() -> {
Thread.sleep(5000);
return "Result";
});
// 无法在这里手动设置future的结果,只能等待或取消
System.out.println("future.get():" + future.get());
// CompletableFuture - 可以手动完成
CompletableFuture<String> completableFuture = new CompletableFuture<>();
executorService.submit(() -> {
try {
Thread.sleep(5000);
completableFuture.complete("Result");
} catch (InterruptedException e) {
completableFuture.completeExceptionally(e);
}
completableFuture.complete("Result");
});
System.out.println("completableFuture set result start");
// 可以在其他地方手动完成
new Thread(() -> {
completableFuture.complete("Manual Result");
}).start();
System.out.println("completableFuture set result end");
System.out.println("completableFuture.get():" + completableFuture.get());
executorService.shutdown();
}
无法链式处理结果
- 获取结果会阻塞调用线程
- 无法在结果可用时自动触发后续操作
public static void handleResultTest() throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
// Future - 阻塞获取结果后再处理
Future<String> future = executorService.submit(() -> {
Thread.sleep(5000);
return "Hello";
});
String result = future.get(); // 阻塞
result = result + " World"; // 同步处理
System.out.println(result);
// CompletableFuture - 非阻塞链式处理
CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(5000);
return "Hello";
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
})
.thenApply(s -> s + " World") // 结果可用时自动处理
.thenAccept(System.out::println); // 非阻塞
System.out.println("not wait");
Thread.sleep(10000);
executorService.shutdown();
}
无法实现响应式编程
- 不支持回调机制
- 无法构建复杂的异步处理流水线
// Future - 无法实现复杂流水线
Future<String> future = executor.submit(task1);
// 想要在完成后执行task2,必须阻塞等待
String result1 = future.get();
Future<String> future2 = executor.submit(() -> task2(result1));
// ...
// CompletableFuture - 流畅的流水线
CompletableFuture.supplyAsync(task1)
.thenApplyAsync(result1 -> task2(result1))
.thenComposeAsync(result2 -> task3(result2))
.thenAccept(finalResult -> ...);
无法组合多个异步操作
- 难以表达"当A和B都完成时,执行C"这样的逻辑
- 需要手动编写复杂的等待和合并代码
public static void combinationTest() throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(2);
// 使用Supplier
Supplier<String> task1 = () -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "Hello1";
};
Supplier<String> task2 = () -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "Hello2";
};
// Future - 使用Callable
Callable<String> callable1 = () -> {
Thread.sleep(2000);
return "Hello1";
};
Callable<String> callable2 = () -> {
Thread.sleep(3000);
return "Hello2";
};
Future<String> future1 = executor.submit(callable1);
Future<String> future2 = executor.submit(callable2);
// 需要手动等待两个都完成
String result1 = future1.get();
String result2 = future2.get();
String combined = result1 + result2;
System.out.println(combined);
// CompletableFuture - 使用Supplier
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(task1, executor);
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(task2, executor);
cf1.thenCombine(cf2, (r1, r2) -> r1 + r2)
.thenAccept(System.out::println);
System.out.println("not wait");
// 等待CompletableFuture完成
Thread.sleep(4000);
executor.shutdownNow();
}
没有异常处理机制
- 异常只能通过get()方法抛出
- 无法在任务完成后自动处理异常
public static void handleError() throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(1);
boolean someCondition = true;
// Future - 异常处理笨拙
Future<String> future = executor.submit(() -> {
if (someCondition) throw new RuntimeException("Error");
return "Success";
});
try {
String result = future.get();
} catch (ExecutionException e) {
// 必须在这里处理异常
System.out.println("Task failed: " + e.getCause());
}
// CompletableFuture - 灵活异常处理
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
if (someCondition) throw new RuntimeException("Error");
return "Success";
}).exceptionally(ex -> {
System.out.println("Task failed: " + ex.getMessage());
return "Fallback Value";
});
System.out.println(completableFuture.get());
executor.shutdown();
}
CompletableFuture优势
- 显式完成设置(手动complete)
- 支持异步回调
- 提供丰富的组合方法
2、关键方法分类
1. 创建CompletableFuture
// 使用默认线程池(ForkJoinPool.commonPool())
CompletableFuture.supplyAsync(Supplier<U> supplier)
CompletableFuture.runAsync(Runnable runnable)
// 使用自定义线程池
CompletableFuture.supplyAsync(Supplier<U> supplier, Executor executor)
2. 结果处理
同步处理:
thenApply(Function<T,U>) // 转换结果
thenAccept(Consumer<T>) // 消费结果
thenRun(Runnable) // 不消费结果执行操作
异步处理:
thenApplyAsync()
thenAcceptAsync()
thenRunAsync()
3. 组合多个Future
thenCompose(Function<T, CompletableFuture<U>>) // 扁平化,避免嵌套
thenCombine(CompletionStage<U>, BiFunction<T,U,V>) // 合并两个独立结果
4. 并行执行
allOf(CompletableFuture<?>...) // 所有完成
anyOf(CompletableFuture<?>...) // 任意一个完成
5. 异常处理
exceptionally(Function<Throwable, T>) // 捕获异常并返回默认值
handle(BiFunction<T, Throwable, U>) // 无论成功失败都处理
whenComplete(BiConsumer<T, Throwable>) // 不改变结果,只处理
3、一些练习和引发的思考总结
练习1
/**
* 异步任务创建
* 创建一个 CompletableFuture,异步计算 1 到 100 的和
* 使用 thenAccept 打印结果
* 确保主线程等待任务完成
*/
@Test
public void test1() {
// orTimeout可设置任务超时时间
CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.supplyAsync(() -> {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
try {
System.out.println("sleep");
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
return sum;
}).orTimeout(10, TimeUnit.SECONDS);
System.out.println("异步线程开始");
integerCompletableFuture.thenAccept(result-> System.out.println("执行thenAccept"+"结果:"+result));
System.out.println("异步线程未完成,thenAccept未执行");
// 确保主线程等待任务完成
// 阻塞等待
integerCompletableFuture.join();
System.out.println("异步线程结束,主线程结束");
}
CompletableFuture 的join 和get 都是阻塞等待,有什么区别
CompletableFuture 中的 join() 和 get() 方法都是用于阻塞当前线程,直到异步任务完成并返回结果,但它们有一些关键区别:
1. 异常处理方式不同
get():
- 抛出 受检异常:
InterruptedException和ExecutionException ExecutionException包装了异步任务中抛出的原始异常- 必须用
try-catch处理或声明抛出
try {
String result = future.get();
} catch (InterruptedException | ExecutionException e) {
// 处理异常
}
join():
- 抛出 非受检异常:
CompletionException(包装了原始异常) - 不需要强制捕获,但如果异步任务失败会抛出运行时异常
- 更适用于流式调用或 Lambda 表达式
String result = future.join(); // 可能抛出 CompletionException
2. 方法来源不同
get():
- 继承自
Future接口 - 是 Java 5 引入的原始
FutureAPI 的一部分
join():
- 是
CompletableFuture类新增的方法 - Java 8 引入,专为函数式编程设计
3. 使用场景
优先用 join():
- 在链式调用或 Lambda 表达式中(避免破坏代码流畅性)
- 当确定任务一定会成功完成时
- 需要简洁代码的场景
CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World")
.thenAccept(System.out::println)
.join(); // 简洁的链式调用
用 get():
- 需要明确处理中断(
InterruptedException)的场景 - 需要访问原始异常时(通过
ExecutionException.getCause()) - 与旧的
Future代码兼容时
try {
String result = future.get(2, TimeUnit.SECONDS); // 带超时的版本
} catch (TimeoutException e) {
// 处理超时
}
4. 超时控制
get():
- 有重载版本支持超时:
get(long timeout, TimeUnit unit) - 超时抛出
TimeoutException
future.get(1, TimeUnit.SECONDS); // 等待最多1秒
join():
- 不支持超时(如果需要超时,可以用
orTimeout()或completeOnTimeout()) - Java 9+ 可以通过
completeOnTimeout()实现类似效果:
future.orTimeout(1, TimeUnit.SECONDS).join(); // Java 9+
5. 中断响应
get():
- 响应线程中断(抛出
InterruptedException) - 适合需要处理中断的场景
join():
- 不直接响应中断(但中断会导致任务取消)
- 更适合不关心中断的场景
最佳实践建议
- 在 Lambda 或流式调用中用
join()
保持代码简洁,避免嵌套try-catch。 - 需要精确异常处理时用
get()
特别是需要区分任务失败、线程中断或超时的情况。 - 需要超时控制时
优先用get(timeout, unit)或 Java 9+ 的orTimeout()。
示例对比
// 使用 get() 的完整异常处理
try {
String result = future.get();
System.out.println(result);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
System.out.println("Thread interrupted");
} catch (ExecutionException e) {
System.out.println("Task failed: " + e.getCause());
}
// 使用 join() 的简洁写法
try {
String result = future.join();
System.out.println(result);
} catch (CompletionException e) {
System.out.println("Task failed: " + e.getCause());
}
总结:join() 是为函数式编程设计的更简洁版本,而 get() 提供了更细粒度的异常控制和超时支持。根据场景选择合适的方法。
CompletableFuture 超时后线程池的资源释放问题
当使用 orTimeout() 或 get(timeout, unit) 导致 CompletableFuture 超时时,线程池中的线程是否会释放取决于具体情况。以下是详细分析:
1. orTimeout() 的行为
orTimeout() (Java 9+) 在超时发生时:
- 会将 CompletableFuture 标记为以
TimeoutException完成 - 但不会自动取消底层任务(除非你显式配置)
CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(5000); // 长时间运行任务
return "Result";
} catch (InterruptedException e) {
System.out.println("任务被中断");
return null;
}
})
.orTimeout(1, TimeUnit.SECONDS) // 1秒超时
.exceptionally(ex -> "超时处理");
在这个例子中:
- 1秒后会触发超时
- 但线程池中的任务会继续运行直到完成(5秒)
- 线程在这期间不会被释放
2. 如何确保线程释放
方案1:使用 completeOnTimeout() + 取消任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(5000);
return "Result";
} catch (InterruptedException e) {
System.out.println("任务被取消");
return null;
}
});
future.completeOnTimeout("Timeout Default", 1, TimeUnit.SECONDS);
// 超时后取消任务
future.whenComplete((res, ex) -> {
if (ex instanceof CompletionException &&
ex.getCause() instanceof TimeoutException) {
future.cancel(true); // true表示中断线程
}
});
方案2:结合 ExecutorService 显式关闭
ExecutorService executor = Executors.newFixedThreadPool(1);
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(5000);
return "Result";
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}, executor)
.orTimeout(1, TimeUnit.SECONDS);
future.whenComplete((res, ex) -> {
if (ex != null) {
executor.shutdownNow(); // 强制关闭线程池
}
});
3. 关键结论
- 默认情况下超时不会自动释放线程:
- 超时只是标记 future 完成状态
- 底层任务线程会继续运行
- 需要主动取消:
- 使用
cancel(true)中断线程 - 或直接关闭线程池 (
shutdownNow())
- 使用
- 最佳实践:
- 对长时间任务实现中断机制
- 使用 try-catch 处理
InterruptedException - 超时后清理资源
4. 完整示例
ExecutorService executor = Executors.newFixedThreadPool(2);
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
// 检查中断状态
for (int i = 0; i < 10; i++) {
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
Thread.sleep(1000); // 分阶段工作
}
return "Done";
} catch (InterruptedException e) {
System.out.println("任务被中断");
return null;
}
}, executor);
// 设置超时并处理
future.orTimeout(3, TimeUnit.SECONDS)
.whenComplete((res, ex) -> {
if (ex != null) {
future.cancel(true); // 中断任务
}
executor.shutdown(); // 关闭线程池
});
这样能确保:
- 3秒超时后中断任务
- 释放线程池资源
- 避免线程泄漏
练习2
/**
* 异常处理
* 创建一个可能抛出异常的 CompletableFuture 任务
* 使用 exceptionally 提供默认值 "Error occurred" 当异常发生时
* 验证异常和正常情况下的输出
*/
@Test
public void test2() {
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
int i = new Random().nextInt(100);
if (i > 50) {
throw new RuntimeException("Error occurred");
}
return i;
}).exceptionally(throwable -> {
System.out.println("Error occurred: " + throwable.getMessage());
return 0;
});
completableFuture.thenAccept(result -> System.out.println("结果: " + result));
completableFuture.join();
}
exceptionally 能捕获哪些异常
- 任务中显式抛出的
RuntimeException(如代码所示) - 任何
Throwable(包括Error,除非显式过滤) - 异步执行过程中发生的其他意外错误(如
OutOfMemoryError)
如果需要区分异常类型,可按如下改进
.exceptionally(throwable -> {
Throwable rootCause = throwable.getCause(); // 获取原始异常
if (rootCause instanceof RuntimeException) {
System.out.println("业务异常: " + rootCause.getMessage());
} else if (rootCause instanceof Error) {
System.out.println("系统错误: " + rootCause);
}
return 0;
})
练习3
/**
* 结果转换
* 异步获取一个随机数 (0-100)
* 使用 thenApply 将结果乘以 2
* 使用 thenAccept 打印最终结果
*/
@Test
public void test3() {
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.supplyAsync(() -> {
int i = new Random().nextInt(100);
System.out.println("随机数: " + i);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return i;
}).thenApply(i -> i * 2)
.thenAccept(result -> System.out.println("结果: " + result));
System.out.println("等待异步任务");
voidCompletableFuture.join();
System.out.println("异步任务结束,主线程结束");
}
supplyAsync和runAsync的使用场景
如何选择?可根据以下问题决定:
- 是否需要任务返回值?
- 是 →
supplyAsync - 否 →
runAsync
- 是 →
- 任务是否会产生需要处理的异常?
supplyAsync可通过返回结果传递异常runAsync需通过whenComplete()/handle()处理
- 是否是纯副作用操作?
- 如日志、通知等 →
runAsync
- 如日志、通知等 →
两者对比
即使不需要返回值,技术上也可以使用 supplyAsync但根据场景选择更合适的方法能让代码更清晰、语义更明确
1. 技术可行性
从语法层面,完全可以用 supplyAsync 替代 runAsync,只需返回 null 或任意虚拟值:
// 不推荐但可行的写法
CompletableFuture.supplyAsync(() -> {
doSomethingWithoutResult(); // 无返回值的操作
return null; // 强制返回null
});
// 等效的runAsync写法(推荐)
CompletableFuture.runAsync(() -> doSomethingWithoutResult());
2. 为什么推荐用 runAsync?
(1)语义清晰性
runAsync明确表示这是一个 无返回值的副作用操作(如日志、通知、清理资源等)supplyAsync会让人误以为需要处理返回值
(2)避免不必要的包装
supplyAsync需要强制返回一个值(即使是null),属于冗余代码runAsync直接使用Runnable,更符合无返回值场景的语义
(3)类型系统更干净
runAsync返回CompletableFuture<Void>,明确告知调用者“无结果”supplyAsync返回CompletableFuture<U>,可能误导他人以为需要处理返回值
3. 何时可以破例使用 supplyAsync?
场景:需要同时满足以下条件时
- 主逻辑不关心返回值,但 需要知道任务是否完成
- 任务内部 可能抛出异常,且需要捕获异常信息
// 示例:用supplyAsync捕获异常信息(虽然不关心返回值)
CompletableFuture.supplyAsync(() -> {
doSomethingRisky();
return null; // 不关心返回值
}).exceptionally(ex -> {
System.err.println("任务失败: " + ex.getMessage());
return null;
});
对比 runAsync 的异常处理:
CompletableFuture.runAsync(() -> doSomethingRisky())
.whenComplete((v, ex) -> {
if (ex != null) {
System.err.println("任务失败: " + ex.getMessage());
}
});
4. 性能差异
- 无实质区别:两者底层使用相同的线程池机制
- 微小开销:
supplyAsync多了一次null的包装/传递,但可忽略不计
5. 最佳实践总结
| 场景 | 推荐方法 | 示例 |
|---|---|---|
| 不关心结果,也不关心异常 | runAsync | 日志记录、触发通知 |
| 不关心结果,但需要异常处理 | runAsync + whenComplete | 异步清理资源时需要错误日志 |
| 需要返回虚拟值以传递状态 | supplyAsync | 某些框架强制要求非Void泛型 |
| 需要链式调用统一风格 | 根据团队约定选择 | 如果链中其他操作用 supplyAsync 可保持风格一致 |
6. 完整示例对比
使用 runAsync(推荐)
// 清晰的“无返回值”语义
CompletableFuture.runAsync(() -> {
log.info("Async operation started");
cleanTempFiles(); // 无返回值的操作
}).whenComplete((v, ex) -> {
if (ex != null) {
log.error("Cleanup failed", ex);
}
});
使用 supplyAsync(不推荐但可行)
// 语义模糊(为什么用supplyAsync却返回null?)
CompletableFuture.supplyAsync(() -> {
log.info("Async operation started");
cleanTempFiles();
return null; // 冗余
}).exceptionally(ex -> {
log.error("Cleanup failed", ex);
return null;
});
结论
- 优先用
runAsync:无返回值时更符合语义,代码更干净 - 特殊场景可用
supplyAsync:如需要统一异常处理风格或框架限制时 - 强制用
supplyAsync返回null是代码异味(除非有充分理由)
练习4
/**
* 任务链式调用
* 创建第一个任务异步生成一个随机字符串
* 第二个任务将字符串转换为大写
* 第三个任务将字符串反转
* 最后打印最终结果
*/
@Test
public void test4() {
CompletableFuture.supplyAsync(() -> "hello world")
.thenApply(String::toUpperCase)
.thenApply(s -> new StringBuilder(s).reverse().toString())
.thenAccept(result -> System.out.println("结果: " + result))
.join();
System.out.println("异步任务结束,主线程结束");
}
练习5
/**
* 两个独立任务组合
* 创建两个独立任务:一个计算圆的面积(给定半径),一个计算正方形面积(给定边长)
* 使用 thenCombine 将两个结果相加
* 打印总面积
*/
@Test
public void test5() {
CompletableFuture<Double> circleAreaFuture = CompletableFuture.supplyAsync(() -> {
double radius = 5.0;
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return Math.PI * radius * radius;
});
CompletableFuture<Double> squareAreaFuture = CompletableFuture.supplyAsync(() -> {
double sideLength = 4.0;
System.out.println("计算正方形面积完成");
return sideLength * sideLength;
});
System.out.println("等待异步任务");
circleAreaFuture.thenCombine(squareAreaFuture, (circleArea, squareArea) -> circleArea + squareArea)
.thenAccept(totalArea -> System.out.println("总面积: " + totalArea))
.join();
System.out.println("异步任务结束,主线程结束");
}
练习6
/**
* 扁平化处理
* 创建第一个任务异步返回一个用户ID
* 使用 thenCompose 根据用户ID异步获取用户详细信息
* 打印用户信息
*/
@Test
public void test6() {
CompletableFuture.supplyAsync(() -> 1)
.thenCompose(userId -> CompletableFuture.supplyAsync(() -> new User(Integer.toString(userId))))
.thenAccept(user -> System.out.println("用户信息: " + user))
.join();
}
}
class User {
String id;
String name;
User(String id) {
this.id = id;
this.name = "User-" + id;
}
@Override
public String toString() {
return "User{id='" + id + "', name='" + name + "'}";
}
}
thenCompose是Java 8中CompletableFuture类的一个重要方法,用于将多个异步操作串联起来,形成异步操作的流水线。
thenCompose方法用于将一个CompletableFuture的结果作为另一个CompletableFuture的输入,实现异步操作的链式调用。它类似于Stream API中的flatMap操作。
thenCompose实际应用场景
- 依赖异步操作:当一个异步操作的结果是另一个异步操作的输入时
- 避免嵌套Future:解决
CompletableFuture<CompletableFuture<T>>的问题(可用来解决嵌套Future问题) - 构建异步流水线:创建一系列依赖的异步操作链
练习7
// 模拟网络请求的方法
private static String mockNetworkRequest(String taskName, int delaySeconds) {
try {
TimeUnit.SECONDS.sleep(delaySeconds);
return taskName + " completed in " + delaySeconds + " seconds";
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
/**
* 多任务并行
* 创建3个异步任务,分别模拟不同时间的网络请求(1s, 2s, 3s)
* 使用 allOf 等待所有任务完成
* 收集并打印所有结果
*/
@Test
public void test7() {
CompletableFuture<String> completableFuture1 = CompletableFuture.supplyAsync(() -> mockNetworkRequest("Task1", 1));
CompletableFuture<String> completableFuture2 = CompletableFuture.supplyAsync(() -> mockNetworkRequest("Task2", 2));
CompletableFuture<String> completableFuture3 = CompletableFuture.supplyAsync(() -> mockNetworkRequest("Task3", 3));
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(completableFuture1, completableFuture2, completableFuture3);
// voidCompletableFuture.thenAccept(v -> {
// System.out.println("所有任务完成");
// System.out.println("结果: " + completableFuture1.join());
// System.out.println("结果: " + completableFuture2.join());
// System.out.println("结果: " + completableFuture3.join());
// }).join();
// voidCompletableFuture.thenRun(() -> {
// System.out.println("所有任务完成");
// System.out.println("结果: " + completableFuture1.join());
// System.out.println("结果: " + completableFuture2.join());
// System.out.println("结果: " + completableFuture3.join());
// }).join();
// 先收集结果,在打印
List<CompletableFuture<String>> futures = Arrays.asList(completableFuture1, completableFuture2, completableFuture3);
CompletableFuture<Void> allDone = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
CompletableFuture<List<String>> allResults = allDone.thenApply(v ->
futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList())
);
// 打印结果
allResults.thenAccept(results -> {
System.out.println("\n所有任务完成");
results.forEach(result -> System.out.println("结果: " + result));
}).join();
}
thenAccept 和 thenRun 的区别及使用场景
thenAccept 和 thenRun 都是 CompletableFuture 的回调方法,但它们有不同的用途:
主要区别
| 方法 | 参数 | 返回值 | 使用场景 |
|---|---|---|---|
thenRun | 无 | 无 | 当前阶段完成后执行一个不需要参数也不返回值的动作 |
thenAccept | 前一阶段的结果 | 无 | 当前阶段完成后消费前一阶段的结果 |
区别分析
- 参数不同:
thenAccept接收一个Consumer,会传入allOf的结果(这里是Void类型)thenRun接收一个Runnable,不接受任何参数
- 适用性:
- 在这个特定场景下,两者都可以工作,因为:
allOf返回的是Void,thenAccept的参数v实际上是null- 你并没有真正使用这个参数
- 在这个特定场景下,两者都可以工作,因为:
- 代码风格:
- 使用
thenRun更合适,因为你不需要前一阶段的结果 - 使用
thenAccept并接受一个不使用的参数,在代码风格上不够优雅
- 使用
推荐用法
在这个场景下,thenRun 是更好的选择,因为:
- 你不需要
allOf的结果(它只是表示完成状态) - 代码更清晰地表达了"当所有任务完成后,执行某些操作"的意图
- 避免了接收一个不使用的参数
使用建议
- 当你需要基于前一阶段的结果执行操作时,使用
thenAccept - 当你只需要在前一阶段完成后执行操作,而不关心其结果时,使用
thenRun - 在测试代码中,通常使用
join()来阻塞等待结果,但在生产代码中应避免这样做
对比 get() 和 join()
join() 会:
- 阻塞当前线程,直到 `task1 完成(如果还没完成的话)。
- 返回
task的结果
关键点:
join()是阻塞的:它会等待任务完成(如果还没完成的话),然后返回结果。- 返回值就是
supplyAsync里Supplier的返回值:- 你的
Supplier是() -> mockNetworkRequest("Task1", 1),所以join()返回的就是mockNetworkRequest的返回值。
- 你的
- 如果任务抛出异常,
join()会抛出CompletionException:- 例如,如果
mockNetworkRequest里抛出异常,task1.join()会抛出CompletionException。
- 例如,如果
| 方法 | 是否受检异常 | 是否抛出 InterruptedException | 是否抛出 ExecutionException | 是否抛出 CompletionException |
|---|---|---|---|---|
get() | ✅ 是 (InterruptedException, ExecutionException) | ✅ 是 | ✅ 是 | ❌ 否 |
join() | ❌ 否 | ❌ 否 | ❌ 否 | ✅ 是 |
join()更简洁(不需要try-catch受检异常),适合测试或确定任务不会失败的情况。get()更适合生产代码,因为它能更明确地处理异常。
总结:
task1.join() 返回的是 mockNetworkRequest 的 return 值,因为:
supplyAsync会执行传入的Supplier并返回它的计算结果。join()会等待任务完成并返回这个计算结果。
练习8
/**
* 最快响应
* 创建3个异步任务,模拟不同响应时间的服务(随机1-3秒)
* 使用 anyOf 获取第一个完成的结果
* 打印最快响应的结果
*/
@Test
public void test8() {
CompletableFuture<String> completableFuture1 = CompletableFuture.supplyAsync(() -> mockNetworkRequest("Task1", new Random().nextInt(3) + 1));
CompletableFuture<String> completableFuture2 = CompletableFuture.supplyAsync(() -> mockNetworkRequest("Task2", new Random().nextInt(3) + 1));
CompletableFuture<String> completableFuture3 = CompletableFuture.supplyAsync(() -> mockNetworkRequest("Task3", new Random().nextInt(3) + 1));
CompletableFuture<Object> anyOf = CompletableFuture.anyOf(completableFuture1, completableFuture2, completableFuture3);
anyOf.thenAccept(result -> System.out.println("最快响应的结果: " + result)).join();
}
练习9
/**
* 超时处理
* 创建一个可能长时间运行的任务(模拟5秒)
* 使用 completeOnTimeout 设置2秒超时和默认值
* 验证超时和正常完成两种情况
*/
@Test
public void test9() {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(5);
return "Task completed";
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
future.completeOnTimeout("Task timed out", 2, TimeUnit.SECONDS)
.thenAccept(result -> System.out.println("Result: " + result)).join();
// future.completeOnTimeout("Task timed out", 6, TimeUnit.SECONDS)
// .thenAccept(result -> System.out.println("Result: " + result)).join();
}
超时后资源释放的问题
completeOnTimeout 的行为特点
- 非中断性:
completeOnTimeout不会中断或取消原始任务- 它只是当超时发生时,让返回的新
CompletableFuture提前完成 - 原始任务会继续在后台执行直到完成
- 它只是当超时发生时,让返回的新
- 结果处理:
- 如果原始任务在超时前完成,使用原始任务的结果
- 如果超时先发生,使用指定的默认值(“Task timed out”)
- 资源消耗:
- 即使超时返回了默认值,原始线程仍会继续执行
- 这可能导致资源浪费(线程、内存等)
如何真正终止任务?
如果需要真正终止长时间运行的任务,应该使用以下模式:
方案1:使用 orTimeout + 取消处理
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(5);
return "Task completed";
} catch (InterruptedException e) {
throw new RuntimeException("Task interrupted", e);
}
});
future.orTimeout(2, TimeUnit.SECONDS) // 超时抛出 TimeoutException
.exceptionally(ex -> {
if (ex.getCause() instanceof TimeoutException) {
// 这里可以添加资源清理逻辑
return "Task timed out and canceled";
}
return "Other error occurred";
})
.thenAccept(System.out::println)
.join();
方案2:配合中断机制
CompletableFuture<String> future = new CompletableFuture<>();
Thread taskThread = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(5);
future.complete("Task completed");
} catch (InterruptedException e) {
future.completeExceptionally(new RuntimeException("Task interrupted"));
}
});
taskThread.start();
future.completeOnTimeout("Task timed out", 2, TimeUnit.SECONDS)
.thenAccept(result -> {
System.out.println("Result: " + result);
if (result.equals("Task timed out")) {
taskThread.interrupt(); // 中断线程
}
})
.join();
关键结论
completeOnTimeout不会终止原始任务 - 它只是提前返回默认值- 资源仍在消耗 - 原始任务会继续运行直到完成
- 如需真正终止 - 需要额外实现中断逻辑或使用
orTimeout+ 异常处理 - 生产环境建议 - 对于可能长时间运行的任务,应该实现可中断的设计
练习10
/**
* 复杂流水线
* 模拟电商订单处理流程:
* 异步创建订单
* 并行检查库存和用户信用
* 当两者都通过时,异步进行支付
* 支付成功后发送通知
* 在每个步骤打印日志
* 处理可能的异常情况
*/
@Test
public void test10() {
CompletableFuture<String> orderProcessing = CompletableFuture.supplyAsync(() -> {
System.out.println("1.创建订单");
sleep(1000);
return "Order-123";
});
CompletableFuture<Boolean> inventoryCheck = orderProcessing.thenApplyAsync(orderId -> {
System.out.println("2.开始检查库存");
sleep(1500);
return true;
});
CompletableFuture<Boolean> creditCheck = orderProcessing.thenApplyAsync(orderId -> {
System.out.println("3.开始检查信用");
sleep(2000);
return true;
});
inventoryCheck.thenCombine(creditCheck, (inventoryPassStatus, creditPassStatus) -> {
System.out.println("库存检查情况:" + inventoryPassStatus + ",信用检查情况:" + creditPassStatus);
return inventoryPassStatus && creditPassStatus;
}).thenCompose(passStatus -> {
if (passStatus) {
System.out.println("4.开始支付");
return CompletableFuture.supplyAsync(() -> {
sleep(1000);
return "Payment Successful";
});
} else {
return CompletableFuture.completedFuture("Payment Failed");
}
}).thenAccept(paymentStatus -> {
System.out.println("5.支付结果:" + paymentStatus);
if ("Payment Successful".equals(paymentStatus)) {
System.out.println("6.开始发送通知");
sleep(500);
System.out.println("7.通知发送完成");
} else {
System.out.println("6.支付失败,无需发送通知");
}
}).exceptionally(throwable -> {
System.out.println("发生异常:" + throwable.getMessage());
return null;
}).join();
}
private static void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

1143

被折叠的 条评论
为什么被折叠?



