为什么这段代码的结果是不确定的?

Why is the result of this code non-deterministic?

我希望下面的代码总是打印“Errored”,但有时它会打印“outerinner”。我想要发生的是让“外部”完成,让“内部”使用“外部”的结果生成自己的结果,如果任何一个未来失败,都会失败。我做错了什么?

        CompletableFuture<String> outer = CompletableFuture.supplyAsync(() -> "outer");
        CompletableFuture<String> inner = CompletableFuture.supplyAsync(() -> "inner");
        inner.completeExceptionally(new IllegalArgumentException());
        CompletableFuture<String> both = outer.thenApply(s -> {
            try {
                String i = inner.get();
                return s + i;
            } catch (InterruptedException |ExecutionException e) {
                throw new IllegalStateException(e);
            }
        });

        try {
            String o = both.get();
            System.out.println(o);
        } catch (ExecutionException | InterruptedException e) {
            System.err.println("Errored");
        }

了解正在发生的事情的关键在于 javadoc for completeExceptionally

If not already completed, causes invocations of get() and related methods to throw the given exception.

在您的示例中,您正在创建两个异步完成的 future,然后调用 completeExceptionally 告诉其中一个 future 抛出异常而不是传递结果。

根据您所说的,有时在您调用 completeExceptionallyinner 未来 已经完成 。在这种情况下,completeExceptionally 调用将没有影响(根据规范),随后的 inner.get() 将提供(已经)计算的结果。

这是一个竞争条件。

理解这一点其实很简单。你需要仔细看看这个:

CompletableFuture<String> inner = CompletableFuture.supplyAsync(() -> "inner");

具体来说,您说的是 supplyAsync,这意味着 在另一个线程中 。在您的主线程(或任何其他线程)中,您执行以下操作:inner.completeExceptionally(new IllegalArgumentException());.

哪个线程应该获胜?来自 supplyAsync 的那个还是你 运行 inner.completeExceptionally 的那个?当然,completeExceptionally 的文档提到了这一点......这是一个基本的“竞赛”,哪个线程首先到达 inner“完成”(通常或通过异常)。