为何调用CompletableFuture.completeExceptionally后whenComplete未执行?
来自CompletableFuture官方文档:
非异步方法的依赖完成操作,可能由完成当前CompletableFuture的线程执行,也可能由任何调用完成方法的线程执行。
查看CompletableFuture.complete()的源码可知,它会调用postComplete(),该方法会遍历并尝试完成依赖链。
为验证我的理解,我编写了如下测试程序:
import java.util.concurrent.*; public class TestCompletableFuture { public static void main(String[] args) throws Exception { CompletableFuture<Void> future = new CompletableFuture<Void>() .whenComplete((res, exc) -> { System.out.println("inside handle."); if (exc != null) { System.out.println("exception."); } System.out.println("completed."); } ); future.completeExceptionally(new Exception("exception")); System.out.println("done."); } }
程序运行输出:
done.
根据我的理解,主线程调用future.completeExceptionally()时,应该会触发CompletableFuture.whenComplete()中传入的函数,但实际并未执行,请问这是为什么?
核心问题在于:你错误地对**whenComplete返回的新CompletableFuture实例**调用了completeExceptionally,而非完成最初创建的那个原始CompletableFuture。
CompletableFuture的设计是函数式的,绝大多数方法(包括whenComplete)都会返回一个全新的CompletableFuture对象,这个新对象依赖于原始对象的完成状态来触发回调。你代码里的future变量指向的是whenComplete生成的新对象,而非一开始new出来的原始实例。当你调用future.completeExceptionally时,只是完成了这个新对象,但绑定在原始对象上的回调从未被触发——因为原始对象自始至终都处于未完成状态。
修正后的代码
只需保存原始的CompletableFuture实例,对其执行完成操作即可:
import java.util.concurrent.*; public class TestCompletableFuture { public static void main(String[] args) throws Exception { // 保存原始CompletableFuture CompletableFuture<Void> originalFuture = new CompletableFuture<>(); // 为原始实例添加回调 originalFuture.whenComplete((res, exc) -> { System.out.println("inside handle."); if (exc != null) { System.out.println("exception."); } System.out.println("completed."); }); // 完成原始实例,触发回调 originalFuture.completeExceptionally(new Exception("exception")); System.out.println("done."); } }
预期输出
运行修正后的代码,你会得到符合预期的输出(回调内容的输出顺序可能因线程执行时机略有不同,但一定会触发):
inside handle. exception. completed. done.
结合官方文档的描述:此时完成原始CompletableFuture的线程是主线程,所以依赖的whenComplete回调会由主线程执行,完全符合文档中“完成当前CompletableFuture的线程执行依赖操作”的说明。
内容的提问来源于stack exchange,提问作者ssapgosu

