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

标签 java java-8 completable-future

这个问题和这个问题不一样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());
涉及到两个线程。一个调用 supplyAsyncthenApply ,另一个调用 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 代表结果。这里不需要组合使用 thenComposesupplyAsync
请注意,这个例子已经在 this Q&A 中讨论过,它还解决了 future 操作在多个 Stream 操作上的不必要隔离以及错误的序列图。

关于java - 作者使用 thenCompose 而不是 thenComposeAsync 的原因是否正确,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63217097/

相关文章:

java - Java 中的多线程,跟踪成功和失败的任务

java - 如果任何线程中发生异常,则中断所有线程

java - 如何在 Spring RestTemplate 中自定义自动编码(marshal)处理以生成/修改 XML header (编码、DOCTYPE)

jack 的 Android 编译时间非常慢

java - 如何在 Jersey + Java 中从特定索引返回数组列表

java - 将 for 循环转换为 concat String 为 lambda 表达式

java - 我有一个以文件名作为输入的方法,应该返回一个数组作为输出。该文件包含带有科目名称和分数的学生记录

java - 用 Java 8 CompletableFuture 替换 Futures.successfulAsList?

java - 列出包子目录中的类

java - Java困惑: boolean assignment fails in Thread