那么,那么应用程序和然后应用程序同步Java CompletableFuture有什么区别?

2022-08-31 13:31:57

假设我有以下代码:

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

我在这篇博客中读到,每个都在一个单独的线程中执行,并且“同时”(这意味着在完成之前先开始),如果是这样,如果第一步没有完成,第二步的输入参数值是多少?thenApplyAsyncthenApplyAsyncsthenApplyAsyncs

如果不采取第二步,第一步的结果将走向何方?第三步将采取哪一步的结果?

如果第二步必须等待第一步的结果,那么有什么意义?Async

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


答案 1

差异与负责运行代码的有关。每个操作员通常有 3 个版本。ExecutorCompletableFuture

  1. thenApply(fn)- 在由调用它的线程定义的线程上运行,因此您通常无法知道这将在何处执行。如果结果已经可用,它可能会立即执行。fnCompleteableFuture
  2. thenApplyAsync(fn)- 在环境定义的执行器上运行,无论环境如何。因为这通常是.fnCompletableFutureForkJoinPool.commonPool()
  3. thenApplyAsync(fn,exec)- 运行在 .fnexec

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


答案 2

你错误地引用了文章的例子,所以你错误地应用了文章的结论。我在你的问题中看到两个问题:

.then___() 的正确用法是什么

在你引用的两个例子中,第二个函数必须等待第一个函数完成。每当您调用 时,输入都是结果,并且必须等待完成,无论您是否使用命名的方法。文章的结论不适用,因为你错误地引用了它。a.then___(b -> ...)baaAsync

文章中的示例实际上是

CompletableFuture<String> receiver = CompletableFuture.supplyAsync(this::findReceiver);

receiver.thenApplyAsync(this::sendMsg);  
receiver.thenApplyAsync(this::sendMsg);  

请注意,两者都应用于 ,而不是在同一语句中链接。这意味着两个函数都可以在完成后以未指定的顺序启动。(任何顺序假设都取决于实现。thenApplyAsyncreceiverreceiver

更清楚地说:

a.thenApply(b).thenApply(c);表示订单是完成,然后开始,完成,然后开始。
就之间的排序而言,其行为将与上述完全相同。abbca.thenApplyAsync(b).thenApplyAsync(c);abc

a.thenApply(b); a.thenApply(c);表示完成,然后或可以开始,以任何顺序。 而不必等待彼此。
就订单而言,以相同的方式工作。abcbca.thenApplyAync(b); a.thenApplyAsync(c);

在阅读以下内容之前,您应该了解上述内容。以上涉及异步编程,没有它,您将无法正确使用API。下面介绍线程管理,通过它您可以优化程序并避免性能缺陷。但是,如果不正确编写程序,就无法优化程序。


标题为:那么Apply然后ApplyAsync的Java CompletableFuture之间的区别?

我必须指出,编写JSR的人一定混淆了技术术语“异步编程”,并选择了现在使新手和退伍军人感到困惑的名称。首先,没有什么比这些方法的合约更异步的了。thenApplyAsyncthenApply

两者之间的差异与函数在哪个线程上运行有关。提供给 的函数可以在以下任何线程上运行thenApply

  1. 调用complete
  2. 在同一实例上的调用thenApply

而 2 个重载thenApplyAsync

  1. 使用默认值(也称为线程池),或者Executor
  2. 使用提供的Executor

需要注意的是,对于 ,运行时承诺最终使用一些您无法控制的执行器来运行您的函数。如果要控制线程,请使用异步变体。thenApply

如果你的函数是轻量级的,那么哪个线程运行你的函数并不重要。

如果您的函数受到 CPU 密集型限制,则不希望将其留给运行时。如果运行时选取网络线程来运行函数,则网络线程无法花时间处理网络请求,从而导致网络请求在队列中等待更长时间,并且服务器变得无响应。在这种情况下,您希望与自己的线程池一起使用。thenApplyAsync


有趣的事实:异步!=线程

thenApply/thenApplyAsync,以及它们的对应项 /、/、/ 都是异步的!这些函数的异步性质与异步操作最终调用 或 的事实有关。这个想法来自Javascript,它确实是异步的,但不是多线程的。thenComposethenComposeAsynchandlehandleAsyncthenAcceptthenAcceptAsynccompletecompleteExceptionally


推荐