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
操作以及错误的时序图。