繁体   English   中英

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

[英]What is the difference between thenApply and thenApplyAsync of Java CompletableFuture?

假设我有以下代码:

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

thenApply案例:

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

这里 output 将为 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之后),如果是这样,如果是第一步,那么第二步的输入参数值是多少没做完?

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

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

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

区别在于负责运行代码的Executor CompletableFuture上的每个算子通常有 3 个版本。

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

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

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

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

在您引用的两个示例中(文章中没有),第二个函数必须等待第一个函数完成。 每当你调用a.then___(b -> ...)输入b的结果是a和必须等待a来完成,不管你是否使用命名方法Async与否。 这篇文章的结论不适用,因为你错误地引用了它。

文章中的例子其实是

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

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

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

更清楚地说:

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

a.thenApply(b); a.thenApply(c); 表示a结束,然后bc可以以任何顺序开始。 bc不必互相等待。
a.thenApplyAync(b); a.thenApplyAsync(c); 就顺序而言,工作方式相同。

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


如题:Java CompletableFuture 的thenApplythenApplyAsync的区别?

我必须指出,编写 JSR 的人一定混淆了技术术语“异步编程”,并选择了现在让新手和老手都感到困惑的名称。 首先, thenApplyAsync中没有任何东西比这些方法的契约中的thenApply更异步。

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

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

thenApplyAsync的 2 个重载要么

  1. 使用默认的Executor (又名线程池),或
  2. 使用提供的Executor

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

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

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


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

thenApply / thenApplyAsync ,以及它们的对应物thenCompose / thenComposeAsynchandle / handleAsyncthenAccept / thenAcceptAsync ,都是异步的! 这些函数的异步特性与异步操作最终调用completecompleteExceptionally的事实有关。 这个想法来自 Javascript,它确实是异步的,但不是多线程的。

这是文档中关于CompletableFuture's thenApplyAsync

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

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

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

让我们切换它。 在某些情况下,“async result: 2”将首先打印,而在某些情况下,“sync result: 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));

第二步(即计算)将始终在第一步之后执行。

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

在这种情况下,异步意味着您可以保证该方法将快速返回并且计算将在不同的线程中执行。

当调用thenApply (没有异步)时,你就没有这样的保证。 在这种情况下,如果 CompletableFuture 在调用方法时已经完成,则计算可以同步执行,即在调用thenApply的同一线程中。 但是计算也可以由完成 future 的线程或调用同一CompletableFuture上的方法的某个其他线程异步执行。 这个答案: https ://stackoverflow.com/a/46062939/1235217 详细解释了 thenApply 做什么和不保证什么。

那么什么时候应该使用thenApply ,什么时候使用thenApplyAsync呢? 我使用以下经验法则:

  • 非异步:仅当任务非常小且非阻塞时,因为在这种情况下我们不关心哪个可能的线程执行它
  • 异步(通常以显式执行器作为参数):用于所有其他任务

thenApplyAsyncthenApply Consumer<? super T> action Consumer<? super T> action将被异步调用,并且不会阻塞指定消费者的线程。

区别在于哪个线程将负责调用方法Consumer#accept(T t)

考虑如下AsyncHttpClient调用:注意下面打印的线程名称。 我希望它能让您清楚区别:

// running in the main method
// public static void main(String[] args) ....

CompletableFuture<Response> future =
    asyncHttpClient.prepareGet(uri).execute().toCompletableFuture();

log.info("Current Thread " + Thread.currentThread().getName());

//Prints "Current Thread main"

thenApply将使用完成未来的同一线程。

//will use the dispatcher threads from the asyncHttpClient to call `consumer.apply`
//The thread that completed the future will be blocked by the execution of the method `Consumer#accept(T t)`.
future.thenApply(myResult -> {
    log.info("Applier Thread " + Thread.currentThread().getName());
    return myResult;
})

//Prints: "Applier Thread httpclient-dispatch-8"

thenApplyAsync将使用 Executor 池中的线程。

//will use the threads from the CommonPool to call `consumer.accept`
//The thread that completed the future WON'T be blocked by the execution of the method `Consumer#accept(T t)`.
future.thenApplyAsync(myResult -> {
    log.info("Applier Thread " + Thread.currentThread().getName());
    return myResult;
})

//Prints: "Applier Thread ForkJoinPool.commonPool-worker-7"

future.get()会阻塞主线程。

//If called, `.get()` may block the main thread if the CompletableFuture is not completed.
future.get();

结论

thenApplyAsync方法中的Async后缀表示完成未来的线程不会被Consumer#accept(T t) method的执行阻塞。

thenApplyAsyncthenApply的用法取决于您是否要阻止线程完成未来。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM