问题 Java CompletableFuture的thenApply和thenApplyAsync有什么区别?


假设我有以下代码:

CompletableFuture<Integer> future  
        = CompletableFuture.supplyAsync( () -> 0);

thenApply 案件:

future.thenApply( x -> x + 1 )
      .thenApply( x -> x + 1 )
      .thenAccept( x -> System.out.println(x));

这里的输出将是2.现在是的情况 thenApplyAsync

future.thenApplyAsync( x -> x + 1 )   // first step
      .thenApplyAsync( x -> x + 1 )   // second step
      .thenAccept( x -> System.out.println(x)); // third step

我读到了这个 博客 每一个 thenApplyAsync 在一个单独的线程和'同时'执行(这意味着跟随 thenApplyAsyncs 在之前开始 thenApplyAsyncs 完成),如果是这样,如果第一步未完成,第二步的输入参数值是多少?

如果不是第二步,第一步的结果会在哪里? 第三步将采取哪一步的结果?

如果第二步必须等待第一步的结果那么重点是什么 Async

这里x - > x + 1只是为了表明这一点,我想知道的是在非常长的计算情况下。


2506
2017-11-25 18:37


起源

你在IDE调试器中试过这个吗?看起来你可以通过一些很好的断点很容易地弄清楚发生了什么。 - Jim Garrison
有趣的问题!我在你的文字中添加了一些格式,我希望没关系。请注意,您可以在内联代码周围使用“`”将其格式化为代码,并且您需要一个空行来创建一个新段落。 - Lii
除了'thenApply'的情况,我是并发新手并没有太多练习,我天真的印象是并发代码问题难以跟踪,所以不要自己尝试,我希望有人可以给我一个确定的答案,以清除我的困惑。 @JimGarrison - Yulin
谢谢你的更正。 @Lii - Yulin
@Lii不知道有接受答案的操作,现在接受一个答案。谢谢! - Yulin


答案:


与...有所不同 Executor 负责运行代码。每个运营商都在 CompletableFuture 通常有3个版本。

  1. thenApply(fn)  - 跑 fn 在由...定义的线程上 CompleteableFuture 它被称为,所以你通常不知道这将被执行。如果结果已经可用,它可能会立即执行。
  2. thenApplyAsync(fn)  - 跑 fn 在环境定义的执行程序上,无论情况如何。对于 CompletableFuture 这通常是 ForkJoinPool.commonPool()
  3. thenApplyAsync(fn,exec)  - 跑 fn 上 exec

最后结果是相同的,但调度行为取决于方法的选择。


9
2017-11-25 19:15



很好的答案,很好地得到关于所有差异版本的解释 thenApply。但我想你错过了这个问题的一部分:“有什么意义?”何时以及为何使用 thenApplyAsync 代替 thenApply?他们会在同一时间运行,不是吗?差别似乎只在于它们运行的​​线程,但这应该不重要,因为两者都可能在某个线程池线程上运行。 - Lii
它是一个链条,链条中的每个调用都取决于前一部分的完成。你用 thenApplyAsync 如果需要在预定义的执行程序上执行该函数。一个例子是在处理UI更新时在UI线程上执行,作为对未来结果的响应。 - Kiskae
是否与'thenApply'相比,'thenApplyAsync'不会阻止当前线程,在其他方面没有区别?假设任务非常昂贵。 - Yulin
thenApplyAsync 保证不阻止当前的线程,而 thenApply 取决于它的行为被调用的对象。 - Kiskae
Kiskae我刚刚运行了这个实验,在CompletableFuture上调用thenApply然后在另一个线程上执行了Apply。你确定你的解释是正确的吗?也许我没有正确理解。然后应用只在前一个函数返回后才执行吗?如果是这样,thenApply总是在与前一个函数相同的线程上执行是否有意义?这样,一旦执行了前面的函数,它的线程现在可以自由执行thenApply。我在这里错过了什么吗? - Boris


这就是文档所说的内容 CompletableFuture's  thenApplyAsync

返回一个新的CompletionStage,当此阶段完成时   通常,使用此阶段的默认异步执行   执行工具,以此阶段的结果作为参数   提供的功能。

所以, thenApplyAsync 必须等待前一个 thenApplyAsync's 结果:

在您的情况下,您首先执行同步工作,然后执行异步工作。因此,第二个是异步的并不重要,因为它只在同步工作完成后启动。

我们把它换掉吧。在某些情况下,将首先打印“异步结果:2”,在某些情况下,将首先打印“同步结果:2”。这里有所不同,因为调用1和2都可以异步运行,在一个单独的线程上调用1,在另一个线程上调用2,这可能是主线程。

CompletableFuture<Integer> future
                = CompletableFuture.supplyAsync(() -> 0);

future.thenApplyAsync(x -> x + 1) // call 1
                .thenApplyAsync(x -> x + 1)
                .thenAccept(x -> System.out.println("async result: " + x));

future.thenApply(x -> x + 1) // call 2
                .thenApply(x -> x + 1)
                .thenAccept(x -> System.out.println("sync result:" + x));

0
2017-11-25 19:09



是否在主线程或其他线程上执行“调用2”取决于状态 future。如果它已经有结果,那么它将在主线程上执行。否则它将注册一个回调并在结果时执行 supplyAsync 可用于任何可能的线程。 - Kiskae
@Kiskae是真的,谢谢 - Willi Mentzel


我必须指出这些名字 thenApply 和 thenApplyAsync 是绝对可怕和令人困惑的。什么都没有 thenApplyAsync 这比异步更多 thenApply 从这些方法的合同。

差异与运行函数的线程有关。提供的功能 thenApply  可以在任何线程上运行 那

  1. 呼叫 complete
  2. 呼叫 thenApply 在同一个实例上

thenApplyAsync 要么使用默认值 Executor (又名。线程池)或提供的 Executor

这些函数的异步部分与异步操作最终调用的事实有关 complete 要么 completeExceptionally。这个想法来自Javascript,它与多线程无关。


0
2017-07-27 15:04



提供的功能 thenApply 也可以在任何调用任意其他,无关的线程上运行 关于那个未来的“完成”方法...我最喜欢的是尝试的线程 cancel 未来… - Holger