简体   繁体   English

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

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

Suppose I have the following code:假设我有以下代码:

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

thenApply case: thenApply案例:

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

Here the output will be 2. Now in case of thenApplyAsync :这里 output 将为 2。现在在thenApplyAsync的情况下:

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

I read in this blog that each thenApplyAsync are executed in a separate thread and 'at the same time'(that means following thenApplyAsyncs started before preceding thenApplyAsyncs finish), if so, what is the input argument value of the second step if the first step not finished?我在这篇博客中读到,每个thenApplyAsync都在一个单独的线程中执行并且“同时”(这意味着在thenApplyAsyncs完成之前开始的thenApplyAsyncs之后),如果是这样,如果是第一步,那么第二步的输入参数值是多少没做完?

Where will the result of the first step go if not taken by the second step?如果不采取第二步,第一步 go 的结果将在哪里? the third step will take which step's result?第三步将采取哪一步的结果?

If the second step has to wait for the result of the first step then what is the point of Async ?如果第二步必须等待第一步的结果,那么Async的意义何在?

Here x -> x + 1 is just to show the point, what I want know is in cases of very long computation.这里 x -> x + 1 只是为了说明这一点,我想知道的是在计算时间很长的情况下。

The difference has to do with the Executor that is responsible for running the code.区别在于负责运行代码的Executor Each operator on CompletableFuture generally has 3 versions. CompletableFuture上的每个算子通常有 3 个版本。

  1. thenApply(fn) - runs fn on a thread defined by the CompleteableFuture on which it is called, so you generally cannot know where this will be executed. thenApply(fn) - 在调用它的CompleteableFuture定义的线程上运行fn ,所以你通常不知道这将在哪里执行。 It might immediately execute if the result is already available.如果结果已经可用,它可能会立即执行。
  2. thenApplyAsync(fn) - runs fn on a environment-defined executor regardless of circumstances. thenApplyAsync(fn) - 无论情况如何,都在环境定义的执行器上运行fn For CompletableFuture this will generally be ForkJoinPool.commonPool() .对于CompletableFuture这通常是ForkJoinPool.commonPool()
  3. thenApplyAsync(fn,exec) - runs fn on exec . thenApplyAsync(fn,exec) - 在exec上运行fn

In the end the result is the same, but the scheduling behavior depends on the choice of method.最终结果是一样的,但调度行为取决于方法的选择。

You're mis-quoting the article's examples, and so you're applying the article's conclusion incorrectly.你错误地引用了文章的例子,所以你错误地应用了文章的结论。 I see two question in your question:我在你的问题中看到两个问题:

What is the correct usage of .then___() .then___()的正确用法是什么

In both examples you quoted, which is not in the article, the second function has to wait for the first function to complete.在您引用的两个示例中(文章中没有),第二个函数必须等待第一个函数完成。 Whenever you call a.then___(b -> ...) , input b is the result of a and has to wait for a to complete, regardless of whether you use the methods named Async or not.每当你调用a.then___(b -> ...)输入b的结果是a和必须等待a来完成,不管你是否使用命名方法Async与否。 The article's conclusion does not apply because you mis-quoted it.这篇文章的结论不适用,因为你错误地引用了它。

The example in the article is actually文章中的例子其实是

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

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

Notice the thenApplyAsync both applied on receiver , not chained in the same statement.请注意thenApplyAsync都应用于receiver ,而不是链接在同一个语句中。 This means both function can start once receiver completes, in an unspecified order.这意味着一旦receiver完成,两个函数都可以以未指定的顺序启动。 (Any assumption of order is implementation dependent.) (任何顺序假设都取决于实现。)

To put it more clearly:更清楚地说:

a.thenApply(b).thenApply(c); means the order is a finishes then b starts, b finishes, then c starts.表示顺序是a完成然后b开始, b完成,然后c开始。
a.thenApplyAsync(b).thenApplyAsync(c); will behave exactly the same as above as far as the ordering between a b c is concerned.a b c之间的排序而言, a行为与上述完全相同。

a.thenApply(b); a.thenApply(c); means a finishes, then b or c can start, in any order.表示a结束,然后bc可以以任何顺序开始。 b and c don't have to wait for each other. bc不必互相等待。
a.thenApplyAync(b); a.thenApplyAsync(c); works the same way, as far as the order is concerned.就顺序而言,工作方式相同。

You should understand the above before reading the below.在阅读以下内容之前,您应该了解以上内容。 The above concerns asynchronous programming, without it you won't be able to use the APIs correctly.以上涉及异步编程,没有它您将无法正确使用 API。 The below concerns thread management, with which you can optimize your program and avoid performance pitfalls.下面涉及线程管理,您可以通过它优化程序并避免性能缺陷。 But you can't optimize your program without writing it correctly.但是如果不正确编写程序,就无法优化程序。


As titled: Difference between thenApply and thenApplyAsync of Java CompletableFuture?如题:Java CompletableFuture 的thenApplythenApplyAsync的区别?

I must point out that the people who wrote the JSR must have confused the technical term "Asynchronous Programming", and picked the names that are now confusing newcomers and veterans alike.我必须指出,编写 JSR 的人一定混淆了技术术语“异步编程”,并选择了现在让新手和老手都感到困惑的名称。 To start, there is nothing in thenApplyAsync that is more asynchronous than thenApply from the contract of these methods.首先, thenApplyAsync中没有任何东西比这些方法的契约中的thenApply更异步。

The difference between the two has to do with on which thread the function is run.两者之间的区别与函数在哪个线程上运行有关。 The function supplied to thenApply may run on any of the threads that提供给thenApply的函数可以在任何线程上运行

  1. calls complete通话complete
  2. calls thenApply on the same instance在同一实例上调用thenApply

while the 2 overloads of thenApplyAsync eitherthenApplyAsync的 2 个重载要么

  1. uses a default Executor (aka thread pool), or使用默认的Executor (又名线程池),或
  2. uses a supplied Executor使用提供的Executor

The take away is that for thenApply , the runtime promises to eventually run your function using some executor which you do not control.需要注意的是,对于thenApply ,运行时承诺最终使用一些您无法控制的执行程序运行您的函数。 If you want control of threads, use the Async variants.如果您想控制线程,请使用 Async 变体。

If your function is lightweight, it doesn't matter which thread runs your function.如果您的函数是轻量级的,那么哪个线程运行您的函数并不重要。

If your function is heavy CPU bound, you do not want to leave it to the runtime.如果您的函数受到大量 CPU 限制,您不希望将它留给运行时。 If the runtime picks the network thread to run your function, the network thread can't spend time to handle network requests, causing network requests to wait longer in the queue and your server to become unresponsive.如果运行时选择网络线程来运行您的函数,则网络线程无法花时间处理网络请求,从而导致网络请求在队列中等待更长时间,并且您的服务器变得无响应。 In that case you want to use thenApplyAsync with your own thread pool.在这种情况下,您希望将thenApplyAsync与您自己的线程池一起使用。


Fun fact: Asynchrony != threads有趣的事实:异步 != 线程

thenApply / thenApplyAsync , and their counterparts thenCompose / thenComposeAsync , handle / handleAsync , thenAccept / thenAcceptAsync , are all asynchronous! thenApply / thenApplyAsync ,以及它们的对应物thenCompose / thenComposeAsynchandle / handleAsyncthenAccept / thenAcceptAsync ,都是异步的! The asynchronous nature of these function has to do with the fact that an asynchronous operation eventually calls complete or completeExceptionally .这些函数的异步特性与异步操作最终调用completecompleteExceptionally的事实有关。 The idea came from Javascript, which is indeed asynchronous but isn't multi-threaded.这个想法来自 Javascript,它确实是异步的,但不是多线程的。

This is what the documentation says about CompletableFuture's thenApplyAsync :这是文档中关于CompletableFuture's thenApplyAsync

Returns a new CompletionStage that, when this stage completes normally, is executed using this stage's default asynchronous execution facility, with this stage's result as the argument to the supplied function.返回一个新的 CompletionStage,当这个阶段正常完成时,使用这个阶段的默认异步执行工具执行,这个阶段的结果作为提供的函数的参数。

So, thenApplyAsync has to wait for the previous thenApplyAsync's result:所以, thenApplyAsync必须等待上一个thenApplyAsync's结果:

In your case you first do the synchronous work and then the asynchronous one.在您的情况下,您首先进行同步工作,然后进行异步工作。 So, it does not matter that the second one is asynchronous because it is started only after the synchrounous work has finished.因此,第二个是异步的并不重要,因为它只有在同步工作完成后才启动。

Let's switch it up.让我们切换它。 In some cases "async result: 2" will be printed first and in some cases "sync result: 2" will be printed first.在某些情况下,“async result: 2”将首先打印,而在某些情况下,“sync result: 2”将首先打印。 Here it makes a difference because both call 1 and 2 can run asynchronously, call 1 on a separate thread and call 2 on some other thread, which might be the main thread.这里有区别,因为调用 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));

The second step (ie computation) will always be executed after the first step.第二步(即计算)将始终在第一步之后执行。

If the second step has to wait for the result of the first step then what is the point of Async?如果第二步必须等待第一步的结果,那么 Async 的意义何在?

Async means in this case that you are guaranteed that the method will return quickly and the computation will be executed in a different thread.在这种情况下,异步意味着您可以保证该方法将快速返回并且计算将在不同的线程中执行。

When calling thenApply (without async), then you have no such guarantee.当调用thenApply (没有异步)时,你就没有这样的保证。 In this case the computation may be executed synchronously ie in the same thread that calls thenApply if the CompletableFuture is already completed by the time the method is called.在这种情况下,如果 CompletableFuture 在调用方法时已经完成,则计算可以同步执行,即在调用thenApply的同一线程中。 But the computation may also be executed asynchronously by the thread that completes the future or some other thread that calls a method on the same CompletableFuture .但是计算也可以由完成 future 的线程或调用同一CompletableFuture上的方法的某个其他线程异步执行。 This answer: https://stackoverflow.com/a/46062939/1235217 explained in detail what thenApply does and does not guarantee.这个答案: https ://stackoverflow.com/a/46062939/1235217 详细解释了 thenApply 做什么和不保证什么。

So when should you use thenApply and when thenApplyAsync ?那么什么时候应该使用thenApply ,什么时候使用thenApplyAsync呢? I use the following rule of thumb:我使用以下经验法则:

  • non-async: only if the task is very small and non-blocking, because in this case we don't care which of the possible threads executes it非异步:仅当任务非常小且非阻塞时,因为在这种情况下我们不关心哪个可能的线程执行它
  • async (often with an explicit executor as parameter): for all other tasks异步(通常以显式执行器作为参数):用于所有其他任务

In both thenApplyAsync and thenApply the Consumer<? super T> actionthenApplyAsyncthenApply Consumer<? super T> action Consumer<? super T> action passed to these methods will be called asynchronously and will not block the thread that specified the consumers. Consumer<? super T> action将被异步调用,并且不会阻塞指定消费者的线程。

The difference have to do with which thread will be responsible for calling the method Consumer#accept(T t) :区别在于哪个线程将负责调用方法Consumer#accept(T t)

Consider an AsyncHttpClient call as below: Notice the thread names printed below.考虑如下AsyncHttpClient调用:注意下面打印的线程名称。 I hope it give you clarity on the difference:我希望它能让您清楚区别:

// 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 same thread that completed the future. 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 Will use the a thread from the Executor pool. 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() Will block the main thread. future.get()会阻塞主线程。

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

Conclusion结论

The Async suffix in the method thenApplyAsync means that the thread completing the future will not be blocked by the execution of the Consumer#accept(T t) method . thenApplyAsync方法中的Async后缀表示完成未来的线程不会被Consumer#accept(T t) method的执行阻塞。

The usage of thenApplyAsync vs thenApply depends if you want to block the thread completing the future or not. thenApplyAsyncthenApply的用法取决于您是否要阻止线程完成未来。

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

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