作者使用 thenCompose 而不是 thenComposeAsync 的原因是否正确

2024-02-24

这个问题和这个问题不一样Java8 thenCompose 和 thenComposeAsync 之间的区别 https://stackoverflow.com/questions/46130969/difference-between-java8-thencompose-and-thencomposeasync因为我想知道作者使用的原因是什么thenCompose并不是thenComposeAsync.

我正在阅读 Modern Java in action,并在第 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页我无法理解:

一般来说,名称中不带 Async 后缀的方法会执行 它的任务与前一个任务在同一线程中,而方法 以异步终止总是将后续任务提交给 线程池,因此每个任务都可以由一个处理 不同的线程。在这种情况下,第二个结果 CompletableFuture 取决于第一个,所以这没有什么区别 最终结果或粗略的时间安排,无论您是否撰写 两个 CompletableFutures 以及此方法的一种或另一种变体

根据我的理解thenCompose( and 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?

这是一个类似的例子,由在这里回答类似问题的人给出:Java8 thenCompose 和 thenComposeAsync 之间的区别 https://stackoverflow.com/questions/46130969/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结果(即报价)。

我的问题是:

作者的想法是否正确:

在这种情况下,第二个结果 CompletableFuture 取决于第一个,所以这没有什么区别 最终结果或粗略的时间安排,无论您是否撰写 两个 CompletableFutures 以及此方法的一种或另一种变体


TL;DR使用是正确的thenCompose代替thenComposeAsync在这里,但不是出于所引用的原因。通常,代码示例不应用作您自己的代码的模板。


本章是 Stackoverflow 上反复出现的话题,为了保持礼貌,我们最好将其描述为“质量不足”。

一般来说,名称中没有 Async 后缀的方法会在与前一个任务相同的线程中执行其任务,...

规范中没有关于执行线程的此类保证。这文档 https://docs.oracle.com/javase/8/docs/api/?java/util/concurrent/CompletableFuture.html says:

  • 为相关完成提供的操作非异步方法可以由完成当前 CompletableFuture 的线程执行,也可以由完成方法的任何其他调用者执行。

因此,该任务也有可能“由完成方法的任何其他调用者”执行。一个直观的例子是

CompletableFuture<X> f = CompletableFuture.supplyAsync(() -> foo())
    .thenApply(f -> f.bar());

涉及两个线程。一个调用supplyAsync and thenApply另一个将调用foo()。如果第二个完成了调用foo()在第一个线程进入执行之前thenApply,有可能未来已经完成了。

未来不记得哪个线程完成了它。它也没有某种神奇的能力来告诉该线程执行某个操作,尽管它可能正忙于其他事情,甚至从那时起就终止了。所以很明显,调用thenApply对于已经完成的 future 不能保证使用完成它的线程。大多数情况下,它会在调用的线程中立即执行该操作thenApply。规范的措辞“完成方法的任何其他调用者”.

但这还不是故事的结局。作为这个答案 https://stackoverflow.com/a/46062939/2711488解释说,当涉及两个以上线程时,该操作也可以由另一个线程同时调用未来的不相关完成方法来执行。这种情况可能很少发生,但在参考实现中是可能的,并且是规范允许的。

我们可以将其总结为: 没有方法Async提供最少的控制超过将执行该操作的线程,甚至可能在调用线程中正确执行该操作,从而导致同步行为。

因此,当执行线程无关紧要并且您不希望后台线程执行(即短暂的非阻塞操作)时,它们是最好的。

而以 Async 终止的方法总是将后续任务提交到线程池,因此每个任务都可以由不同的线程处理。在这种情况下,第二个 CompletableFuture 的结果取决于第一个,……

当你这样做时

future.thenCompose(quote ->
    CompletableFuture.supplyAsync(() -> Discount.applyDiscount(quote), executor))

three涉及未来,所以不太清楚“第二”指的是哪个未来。supplyAsync正在提交一个动作并返回一个未来。提交包含在传递给的函数中thenCompose,这将返回另一个未来。

如果你用过thenComposeAsync在这里,您只要求执行supplyAsync必须提交到线程池,而不是直接在完成线程或“完成方法的任何其他调用者”中执行,例如直接在线程中调用thenCompose.

关于依赖关系的推理在这里没有意义。 “then”总是意味着依赖。如果你使用thenComposeAsync在这里,您强制将操作提交到线程池,但是在完成之前仍然不会发生此提交future。而如果future异常完成,提交根本不会发生。

所以,正在使用thenCompose这里合理吗?是的,是的,但不是因为引用中给出的原因。如前所述,使用非异步方法意味着放弃对执行线程的控制,并且仅应在线程无关紧要时使用,尤其是对于简短的非阻塞操作。呼唤supplyAsync是一个便宜的操作,它将自行将实际操作提交到线程池,因此可以在任何可以自由执行的线程中执行它。

然而,这是一个不必要的复杂化。您可以使用以下方法实现相同的效果

future.thenApplyAsync(quote -> Discount.applyDiscount(quote), executor)

其作用完全相同,提交applyDiscount to executor when future已经完成并产生代表结果的新未来。使用组合thenCompose and supplyAsync这里是不必要的。

请注意,这个例子已经在本次问答 https://stackoverflow.com/q/50854568/2711488已经,这也解决了未来运营在多个方面不必要的隔离Stream操作以及错误的时序图。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

作者使用 thenCompose 而不是 thenComposeAsync 的原因是否正确 的相关文章

随机推荐