问题描述
public final class Start {
private static final CountDownLatch FINAL_THREAD = new CountDownLatch(1);
private static String getValue() {
System.out.println("Waiting...");
try {
Thread.sleep(Duration.ofSeconds(1).toMillis());
return "value";
} catch (InterruptedException e) {
return "interrupted";
}
}
private static void whenComplete(String value,Throwable ex) {
if (ex != null) {
System.out.println("whenComplete Ex: " + ex);
} else {
System.out.println("whenComplete Value: " + value);
}
}
private static String handle(String value,Throwable ex) {
if (ex != null) {
System.out.println("handle Ex: " + ex);
} else {
System.out.println("handle Value: " + value);
}
FINAL_THREAD.countDown();
return value;
}
private static String peek(String value) {
System.out.println("peek: " + value);
return value;
}
private static CompletableFuture<String> createRequest() {
System.out.println("Create....");
return CompletableFuture.supplyAsync(Start::getValue)
.thenApply(Start::peek)
.handle(Start::handle)
.whenComplete(Start::whenComplete);
}
public static void main(String[] args) throws InterruptedException,ExecutionException {
createRequest().completeExceptionally(new RuntimeException("TEST"));
FINAL_THREAD.await();
}
}
当我执行它时,我得到如下输出:
> Task :Start.main()
Create....
Waiting...
peek: value
handle Value: value
BUILD SUCCESSFUL in 10s
我不明白为什么 Start::whenComplete
和 Start::peek
都被调用时不调用 Start::handle
。如果我使用 whenComplete 切换句柄,则不会调用 Start::handle
,但会调用 Start::whenComplete
。我希望在这种情况下 Start::whenComplete
将使用 RuntimeExeception
调用,而其他阶段将使用 Start::getValue
提供的值执行。
解决方法
我认为 CompletableFuture
的文档涵盖了这一点,但让我们慢慢了解它,因为它不是那么简单。首先,我们需要稍微重构您的代码:
private static CompletableFuture<String> createRequest() {
System.out.println("Create....");
CompletableFuture<String> one = CompletableFuture.supplyAsync(Start::getValue);
CompletableFuture<String> two = one.thenApply(Start::peek);
CompletableFuture<String> three = two.handle(Start::handle);
CompletableFuture<String> four = three.whenComplete(Start::whenComplete);
return four;
}
我们也稍微改变一下您的 main
:
public static void main(String[] args) {
CompletableFuture<String> f = createRequest();
boolean didI = f.completeExceptionally(new RuntimeException("TEST"));
System.out.println("have I completed it? : " + didI);
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5));
}
现在让我们仔细看看这个:
CompletableFuture<String> four = three.whenComplete(Start::whenComplete);
通过 whenComplete
的文档:
返回一个与此阶段具有相同结果或异常的新 CompletionStage,在此阶段完成时执行给定的操作。
把它分成小块:
返回与此阶段 (four
) 具有相同结果或异常的新 CompletionStage (three
),当此阶段 (Start::whenComplete
) 执行给定操作 (three
) }}) 完成。
谁应该执行Start::whenComplete
?根据文档:four
。它应该什么时候执行? three
完成后。
根据您的流程,在 three
完成之前,您completeExceptionally
您的four
。因此,当 three
完成时,four
也完成 - 意味着它无法执行 Start::whenComplete
(action
);只是因为它已经完成了。另一种思考方式是,当您的代码到达这一行时:
CompletableFuture<String> four = three.whenComplete(Start::whenComplete);
four
是一个未完成的未来。可以通过两种方式完成:
-
要么在
three
完成时触发Start::whenComplete
-
外部(你用
completeExceptionally
做什么)
因为您在外部在 three
完成之前完成它,所以它不会运行那个动作。
如果你将一个动作链接到你完成的未来:
four.whenComplete(Start::whenComplete);
此时您将看到所需的输出。