这个问题和这个问题不一样Difference between Java8 thenCompose and thenComposeAsync因为我想知道作者使用thenCompose
的原因是什么而不是 thenComposeAsync
.
我正在阅读《现代 Java 实战》,在第 405 页上遇到了这部分代码:
public static List<String> findPrices(String product) {
ExecutorService executor = Executors.newFixedThreadPool(10);
List<Shop> shops = Arrays.asList(new Shop(), new Shop());
List<CompletableFuture<String>> priceFutures = shops.stream()
.map(shop -> CompletableFuture.supplyAsync(() -> shop.getPrice(product), executor))
.map(future -> future.thenApply(Quote::parse))
.map(future -> future.thenCompose(quote ->
CompletableFuture.supplyAsync(() -> Discount.applyDiscount(quote), executor)))
.collect(toList());
return priceFutures.stream()
.map(CompletableFuture::join).collect(toList());
}
一切正常,我可以理解这段代码,但这是作者不使用 thenComposeAsync
的原因。在第 408 页,我无法理解:In general, a method without the Async suffix in its name executes its task in the same threads the previous task, whereas a method terminating with Async always submits the succeeding task to the thread pool, so each of the tasks can be handled by a different thread. In this case, the result of the second CompletableFuture depends on the first,so it makes no difference to the final result or to its broad-brush timing whether you compose the two CompletableFutures with one or the other variant of this method
在我与
thenCompose
的理解(和 thenComposeAsync
)签名如下:public <U> CompletableFuture<U> thenCompose(
Function<? super T, ? extends CompletionStage<U>> fn) {
return uniComposeStage(null, fn);
}
public <U> CompletableFuture<U> thenComposeAsync(
Function<? super T, ? extends CompletionStage<U>> fn) {
return uniComposeStage(asyncPool, fn);
}
第二个CompletableFuture
的结果可以取决于以前的CompletableFuture
在许多情况下(或者我可以说几乎总是),我们应该使用 thenCompose
而不是 thenComposeAsync
在那些情况下?如果我们在第二个
CompletableFuture
中有阻塞代码怎么办? ?这是一个类似的例子,由在这里回答类似问题的人给出:Difference between Java8 thenCompose and thenComposeAsync
public CompletableFuture<String> requestData(Quote quote) {
Request request = blockingRequestForQuote(quote);
return CompletableFuture.supplyAsync(() -> sendRequest(request));
}
我认为在这种情况下使用 thenComposeAsync
可以让我们的程序更快,因为这里 blockingRequestForQuote
可以在不同的线程上运行。但是根据作者的意见,我们不应该使用thenComposeAsync
因为它取决于第一个 CompletableFuture
结果(即报价)。我的问题是:
作者的想法是否正确,他说:
In this case, the result of the second CompletableFuture depends on the first,so it makes no difference to the final result or to its broad-brush timing whether you compose the two CompletableFutures with one or the other variant of this method
最佳答案
TL;DR 在这里使用 thenCompose
而不是 thenComposeAsync
是正确的,但不是因为引用的原因。通常,代码示例不应用作您自己代码的模板。
本章是 Stackoverflow 上反复出现的主题,原因我们可以最好地描述为“质量不足”,以保持礼貌。
In general, a method without the Async suffix in its name executes its task in the same threads the previous task, …
规范中对执行线程没有这样的保证。 documentation 说:
- Actions supplied for dependent completions of non-async methods may be performed by the thread that completes the current CompletableFuture, or by any other caller of a completion method.
因此,任务也有可能“由完成方法的任何其他调用者”执行。一个直观的例子是
CompletableFuture<X> f = CompletableFuture.supplyAsync(() -> foo())
.thenApply(f -> f.bar());
涉及到两个线程。一个调用 supplyAsync
和 thenApply
,另一个调用 foo()
。如果第二个线程在第一个线程进入 foo()
的执行之前完成了 thenApply
的调用,则有可能 future 已经完成了。future 不记得哪个线程完成了它。它也没有一些神奇的能力来告诉该线程执行某个操作,尽管它可能正忙于其他事情,甚至从那时起就已终止。所以很明显,在已经完成的 future 上调用
thenApply
不能保证使用完成它的线程。大多数情况下,它会在调用 thenApply
的线程中立即执行操作。规范的措辞“完成方法的任何其他调用者”涵盖了这一点。但这并不是故事的结局。正如 this answer 解释的那样,当涉及两个以上的线程时,该操作也可以由另一个线程同时在 future 调用无关的完成方法来执行。这可能很少发生,但在引用实现中是可能的,并且是规范允许的。
我们可以将其总结为: 没有 Async 的方法对将执行操作的线程提供最少的控制,甚至可能在调用线程中正确执行,从而导致同步行为。
因此,当正在执行的线程无关紧要并且您不希望后台线程执行时,它们是最好的,即简短的非阻塞操作。
whereas a method terminating with Async always submits the succeeding task to the thread pool, so each of the tasks can be handled by a different thread. In this case, the result of the second CompletableFuture depends on the first, …
当你做
future.thenCompose(quote ->
CompletableFuture.supplyAsync(() -> Discount.applyDiscount(quote), executor))
涉及到三个 future ,所以还不是很清楚,哪个 future 是“第二个”的意思。 supplyAsync
正在提交一个 Action 并返回一个 future 。提交包含在传递给 thenCompose
的函数中,该函数将返回另一个 future 。如果你在这里使用
thenComposeAsync
,你只要求 supplyAsync
的执行必须提交给线程池,而不是直接在完成线程或“完成方法的任何其他调用者”中执行,例如直接在线程中调用 thenCompose
。关于依赖的推理在这里没有意义。 “then”总是意味着依赖。如果在这里使用
thenComposeAsync
,则是强制将 action 提交到线程池,但是在 future
完成之前,仍然不会发生此提交。如果 future
异常完成,则根本不会提交。那么,在这里使用
thenCompose
合理吗?是的,但不是因为给出的原因是报价。如上所述,使用非异步方法意味着放弃对正在执行的线程的控制,并且应该只在线程无关紧要时使用,尤其是对于简短的非阻塞操作。调用 supplyAsync
是一个廉价的 action,它会自己将实际的 action 提交给线程池,所以可以在任何空闲的线程中执行它。然而,这是一个不必要的并发症。您可以使用
future.thenApplyAsync(quote -> Discount.applyDiscount(quote), executor)
这将完全相同,当 applyDiscount
完成时将 executor
提交给 future
并产生一个新的 future 代表结果。这里不需要组合使用 thenCompose
和 supplyAsync
。请注意,这个例子已经在 this Q&A 中讨论过,它还解决了 future 操作在多个
Stream
操作上的不必要隔离以及错误的序列图。
关于java - 作者使用 thenCompose 而不是 thenComposeAsync 的原因是否正确,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63217097/