这个问题不同于Java8 thenCompose和ThenComposeSync之间的区别,因为我想知道作者使用thenCompose
而不是
ThenComposeSync
的原因是什么。
我在阅读《现代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());
}
一切正常,我可以理解这段代码,但以下是作者为什么没有在第408页使用composeasync的原因,我无法理解:
通常,名称中没有Async后缀的方法在与前一个任务相同的线程中执行其任务,而以Async终止的方法总是将后续任务提交到线程池,因此每个任务都可以由不同的线程处理。在这种情况下,第二个CompletableFuture的结果取决于第一个,因此无论您使用此方法的一个变体还是另一个变体来组合两个CompletableFuture,都不会对最终结果或其大致计时产生影响
根据我对
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。在许多情况下(或者我可以说几乎总是这样),在这些情况下,我们应该使用组合而不是复合同步吗?
如果我们在第二个
CompletableFuture
中有阻塞代码怎么办?
这是一个类似的例子,由在这里回答类似问题的人给出:Java8 thenCompose和thenComposeAsync之间的区别
public CompletableFuture<String> requestData(Quote quote) {
Request request = blockingRequestForQuote(quote);
return CompletableFuture.supplyAsync(() -> sendRequest(request));
}
在我看来,在这种情况下,使用composeasync可以使我们的程序更快,因为在这里,blockingRequestForQuote可以在不同的线程上运行。但是根据作者的观点,我们不应该使用复合同步,因为它取决于第一个完整的未来结果(即引号)。
我的问题是:
当作者说:
在这种情况下,第二个CompletableFuture的结果取决于第一个,因此无论您使用此方法的一个或另一个变体组合两个CompletableFutures,最终结果或其粗略的时间都没有区别
霍尔格的回答多么礼貌啊!我真的很惊讶他能提供如此好的解释,同时又不说作者完全错了。在读了同一本书,不得不挠了两次头之后,我也想在这里提供一点0.02美元。
首先,没有“记住”哪个线程在哪个阶段执行,规范也没有做出这样的声明(如上所述)。有趣的部分甚至出现在上面引用的文档中:
为非异步方法的依赖完成提供的操作可以由完成当前CompletableFuture的线程执行,也可以由完成方法的任何其他调用方执行。
即使这样...完成当前CompletableFuture部分也很棘手。如果有两个线程尝试在CompletableFuture
上调用完成
,哪个线程将运行所有依赖操作?实际完成它的那个?还是任何其他?我写了一个jcpress测试,在查看结果时非常不直观:
@JCStressTest
@State
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE, desc = "executed in completion thread")
@Outcome(id = "0, 1", expect = Expect.ACCEPTABLE, desc = "executed in the other thread")
@Outcome(id = "0, 0", expect = Expect.FORBIDDEN)
@Outcome(id = "1, 1", expect = Expect.FORBIDDEN)
public class CompletableFutureWhichThread1 {
private final CompletableFuture<String> future = new CompletableFuture<>();
public CompletableFutureWhichThread1() {
future.thenApply(x -> action(Thread.currentThread().getName()));
}
volatile int x = -1; // different default to not mess with the expected result
volatile int y = -1; // different default to not mess with the expected result
volatile int actor1 = 0;
volatile int actor2 = 0;
private String action(String threadName) {
System.out.println(Thread.currentThread().getName());
// same thread that completed future, executed action
if ("actor1".equals(threadName) && actor1 == 1) {
x = 1;
return "action";
}
// same thread that completed future, executed action
if ("actor2".equals(threadName) && actor2 == 1) {
x = 1;
return "action";
}
y = 1;
return "action";
}
@Actor
public void actor1() {
Thread.currentThread().setName("actor1");
boolean completed = future.complete("done-actor1");
if (completed) {
actor1 = 1;
} else {
actor2 = 1;
}
}
@Actor
public void actor2() {
Thread.currentThread().setName("actor2");
boolean completed = future.complete("done-actor2");
if (completed) {
actor2 = 1;
}
}
@Arbiter
public void arbiter(II_Result result) {
if (x == 1) {
result.r1 = 1;
}
if (y == 1) {
result.r2 = 1;
}
}
}
运行此操作后,可以看到0,1和
1,0。你不需要对测试本身有太多了解,但它证明了一个相当有趣的观点。
您有一个
CompletableFuture
,它有一个future.then应用(x-
你关于速度的理由有点错误。当你把工作分派到不同的线程时,你通常会为此付出代价
thenCompose
vs
thenComposeAsync
是关于能够准确预测您的工作将发生在哪里。正如您在上面所看到的那样,您无法做到这一点,除非您使用采用线程池的异步方法。你的自然问题应该是:“为什么我在乎它在哪里执行?”。
jdk的HttpClient中有一个内部类,叫做SelectorManager。它(从高层次上)有一个相当简单的任务:它从套接字读取数据,并将“响应”返回给等待http结果的线程。本质上,这是一个唤醒所有等待http数据包的相关方的线程。现在想象一下,这个特定的线程在内部进行编译。现在再想象一下,您的调用链如下所示:
httpClient.sendAsync(() -> ...)
.thenApply(x -> foo())
其中,foo是一种永远不会完成的方法(或者需要很多时间才能完成)。由于您不知道实际执行将发生在哪个线程中,因此它很可能发生在SelectorManager线程中。这将是一场灾难。其他所有http调用都会过时,因为该线程现在正忙。因此,组件同步(composeasync):如果需要,让配置的池执行工作/等待,而选择器管理器(SelectorManager)线程可以自由地执行其工作。
所以作者给出的理由是完全错误的。
匿名用户
太长别读这里使用thenCompose
而不是thenComposeAsync
是正确的,但不是出于引用的原因。通常,代码示例不应用作您自己代码的模板。
本章是一个关于堆栈溢出的重复主题,为了保持礼貌,我们最好将其描述为“质量不足”。
通常,名称中没有异步后缀的方法在与前一个任务相同的线程中执行其任务…
规范中没有关于执行线程的此类保证。文件中说:
- 为非异步方法的依赖完成提供的操作可以由完成当前CompletableFuture的线程执行,也可以由完成方法的任何其他调用方执行
因此,任务也有可能是“由完成方法的任何其他调用方”执行的。一个直观的例子是
CompletableFuture<X> f = CompletableFuture.supplyAsync(() -> foo())
.thenApply(f -> f.bar());
涉及两个线程。一个调用supplyAsync和apply,另一个调用foo()。如果第二个线程在第一个线程进入执行然后应用之前完成了对foo()的调用,那么未来可能已经完成。
未来不记得是哪个线程完成了它。它也没有一些神奇的能力来告诉该线程执行一个操作,尽管它可能忙于其他事情,甚至从那时起就终止了。所以很明显,在已经完成的未来调用thenApply
不能promise使用完成它的线程。在大多数情况下,它会立即在调用thenApply
的线程中执行操作。这被规范的措辞“完成方法的任何其他调用者”所涵盖。
但这还不是故事的结尾。正如这个答案所解释的,当涉及到两个以上的线程时,该操作也可以由另一个线程同时在未来调用不相关的完成方法来执行。这可能很少发生,但在参考实现中是可能的,并且是规范允许的。
我们可以将其总结为:没有异步的方法对将执行操作的线程提供最少的控制,甚至可以在调用线程中直接执行它,从而导致同步行为。
因此,当执行的线程无关紧要并且您不希望后台线程执行时,即对于简单的非阻塞操作,它们是最好的。
而以异步终止的方法总是将后续任务提交到线程池,因此每个任务都可以由不同的线程处理。在这种情况下,第二个可完成未来的结果取决于第一个…
当你这么做的时候
future.thenCompose(quote ->
CompletableFuture.supplyAsync(() -> Discount.applyDiscount(quote), executor))
这涉及到三个未来,所以不太清楚“第二个”指的是哪个未来<代码>SupplySync正在提交操作并返回未来。提交包含在传递给compose的函数中,该函数将返回另一个未来。
如果您在这里使用了composeasync,那么您只需要将supplyAsync的执行提交到线程池,而不是直接在完成线程或“完成方法的任何其他调用方”中执行,例如直接在调用compose的线程中执行。
关于依赖关系的推理在这里毫无意义。“then”总是意味着依赖。如果在此处使用composeasync,则强制将操作提交到线程池,但在将来的操作完成之前,此提交仍然不会发生。如果未来的异常完成,提交将根本不会发生。
那么,在这里使用thenCompose
合理吗?是的,但不是因为引用中给出的原因。如前所述,使用非异步方法意味着放弃对正在执行的线程的控制,并且应该仅在线程无关紧要时使用,最显着的是对于简短、非阻塞的操作。调用Supply yAsync
是一种廉价的操作,它会自行将实际操作提交给线程池,因此可以在任何线程自由执行它。
然而,这是一个不必要的复杂问题。您可以使用
future.thenApplyAsync(quote -> Discount.applyDiscount(quote), executor)
这将做完全相同的事情,当未来完成时,将applyDiscount提交给执行者,并生成代表结果的新未来。在这里不需要组合使用合成和供应同步。
请注意,本Q中已经讨论了这个示例
版本: 返回一个,其ID为“z”,偏移量为0,默认区域规则。 返回一个,包含ID“utc”和。 例如,在处理时。在这里,我能发现的唯一区别是它的打印方式不同。 我们正在来回地进行代码审查讨论,所以我想这种冲突并不罕见。 它是一个常量(此外,它的偏移量值(0)甚至被缓存)。 由于缺少区域信息,它的开销少了一点。 在UTC时,不需要考虑夏时制时间或历史变化,就像在任何其他时区一样。 因此,对于我迄今为
问题内容: 我还没有机会在学校参加任何严肃的低级编程课程。(我知道我真的应该继续学习“幕后知识”以成为更好的程序员。)我感谢Java的便利性,包括将任何内容粘贴到语句中的能力。但是,为什么有任何理由要使用它呢? 另外,我应该避免在“实际应用程序”中进行此类打印调用吗?使用某种UI功能将消息打印到客户端的显示屏上可能更好,对吧? 问题答案: PrintStream类的方法提供类似于C中函数的字符串格
问题内容: 我有看起来像这样的XML: 我想使用JAXB进行阅读。 我知道我能做 如果XML看起来像 ,但是我对上面的XML布局怎么办? 问题答案: 注意: 我是EclipseLink JAXB(MOXy)的负责人,并且是JAXB 2.X(JSR-222)专家组的成员。 您可以在此用例中使用MOXy的扩展名:
我对多个渲染目标有一个奇怪的问题。我在FBO上附加了3种纹理:颜色、法线和位置。我可以正确渲染颜色和位置,但渲染正常纹理会产生(绿色和红色是旋转立方体的一部分): 在左下角,是将法线纹理渲染到四边形的结果。 在我的顶点着色器中,我将法线计算为:,在片段着色器中,我将其发射为:
问题内容: 我有这样的HTML代码: 我想单击文本为 Home 的链接。由于此Home链接在登录后出现,因此我具有如下代码: 登录之前的部分效果很好。但是最后一行是有问题的 从HTML代码可以看到,它引发了此NoSuchElementException的Home链接。 请问关于我在做什么错的任何指导? 问题答案: 您是否尝试过为此添加隐式等待,以使其等待而不是快速运行。 该调用使浏览器开始轮询,直
/usr/local/kafka2.12-2.6.0/config/server.properties 在开始动物园管理员和Kafka之后,创建一个新的主题 检查所有三个节点上的集群状态