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));
此处输出为 2。现在如果 thenApplyAsync
:
future.thenApplyAsync( x -> x + 1 ) // first step
.thenApplyAsync( x -> x + 1 ) // second step
.thenAccept( x -> System.out.println(x)); // third step
我在这篇 blog 中读到,每个 thenApplyAsync
都在一个单独的线程中执行,并且 'at the same time'(这意味着 thenApplyAsyncs
在 thenApplyAsyncs
之前开始finish),如果是,如果第一步没有完成,那么第二步的输入参数值是多少?
如果第二步不走第一步的结果会去哪里?
第三步会取哪一步的结果?
如果第二步必须等待第一步的结果那么Async
有什么意义?
这里x -> x + 1只是为了说明问题,我想知道的是在计算时间很长的情况下。
这是文档中关于 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.
因此,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));
区别与负责 运行 代码的 Executor
有关。 CompletableFuture
上的每个算子一般都有3个版本。
thenApply(fn)
- 在调用它的 CompleteableFuture
定义的线程上运行 fn
,因此您通常不知道它将在何处执行。如果结果已经可用,它可能会立即执行。
thenApplyAsync(fn)
- 在环境定义的执行程序上运行 fn
,无论情况如何。对于 CompletableFuture
,这通常是 ForkJoinPool.commonPool()
。
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
b
c
之间的顺序而言,a.thenApplyAsync(b).thenApplyAsync(c);
的行为与上述完全相同。
a.thenApply(b); a.thenApply(c);
表示 a
结束,然后 b
或 c
可以开始,顺序不限。 b
和 c
不必互相等待。
a.thenApplyAync(b); a.thenApplyAsync(c);
工作方式相同,就顺序而言。
在阅读以下内容之前,您应该了解以上内容。以上内容涉及异步编程,没有它您将无法正确使用 API。以下内容涉及线程管理,您可以使用它来优化您的程序并避免性能缺陷。但是如果不正确编写程序就无法优化程序。
如题:JavaCompletableFuture的thenApply
和thenApplyAsync
的区别?
我必须指出,编写 JSR 的人一定混淆了“异步编程”这个技术术语,并选择了现在让新手和老手都感到困惑的名字。首先,thenApplyAsync
中没有什么比这些方法的约定中的 thenApply
更异步的了。
两者的区别与函数在哪个线程上有关运行。提供给 thenApply
的函数
- 调用
complete
- 在同一实例上调用
thenApply
而 thenApplyAsync
的 2 个重载
- 使用默认的
Executor
(a.k.a。线程池),或者
- 使用提供的
Executor
要点是,对于 thenApply
,运行time 承诺最终 运行 您的函数会使用一些您无法控制的执行程序。如果您想控制线程,请使用异步变体。
如果您的函数是轻量级的,那么哪个线程 运行 您的函数并不重要。
如果你的函数是重 CPU 绑定,你不想把它留给 运行 时间。如果 运行 时间选择网络线程来 运行 你的函数,网络线程不能花时间处理网络请求,导致网络请求在队列中等待更长时间,你的服务器变得无响应。在这种情况下,您希望将 thenApplyAsync
与您自己的线程池一起使用。
有趣的事实:异步 != 线程
thenApply
/thenApplyAsync
,以及对应的 thenCompose
/thenComposeAsync
、handle
/handleAsync
、thenAccept
/ thenAcceptAsync
,都是异步的!这些函数的异步性质与异步操作最终 调用complete
或completeExceptionally
这一事实有关。这个想法来自 Javascript,它确实是异步的,但不是多线程的。
第二步(即计算)总是在第一步之后执行。
If the second step has to wait for the result of the first step then what is the point of Async?
Async 在这种情况下意味着您可以保证该方法 return 快速并且计算将在不同的线程中执行。
当调用 thenApply
(没有异步)时,您没有这样的保证。在这种情况下,如果在调用方法时 CompletableFuture 已经完成,则计算可以同步执行,即在调用 thenApply
的同一线程中执行。但是计算也可以由完成未来的线程或在同一 CompletableFuture
上调用方法的某个其他线程异步执行。这个答案: 详细解释了 thenApply 做什么和不保证什么。
那么什么时候应该使用 thenApply
什么时候使用 thenApplyAsync
?我使用以下经验法则:
- 非异步:仅当任务非常小且非阻塞时,因为在这种情况下我们不关心哪个可能的线程执行它
- async(通常带有显式执行器作为参数):用于所有其他任务
假设我有以下代码:
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
我在这篇 blog 中读到,每个 thenApplyAsync
都在一个单独的线程中执行,并且 'at the same time'(这意味着 thenApplyAsyncs
在 thenApplyAsyncs
之前开始finish),如果是,如果第一步没有完成,那么第二步的输入参数值是多少?
如果第二步不走第一步的结果会去哪里? 第三步会取哪一步的结果?
如果第二步必须等待第一步的结果那么Async
有什么意义?
这里x -> x + 1只是为了说明问题,我想知道的是在计算时间很长的情况下。
这是文档中关于 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.
因此,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));
区别与负责 运行 代码的 Executor
有关。 CompletableFuture
上的每个算子一般都有3个版本。
thenApply(fn)
- 在调用它的CompleteableFuture
定义的线程上运行fn
,因此您通常不知道它将在何处执行。如果结果已经可用,它可能会立即执行。thenApplyAsync(fn)
- 在环境定义的执行程序上运行fn
,无论情况如何。对于CompletableFuture
,这通常是ForkJoinPool.commonPool()
。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
b
c
之间的顺序而言,a.thenApplyAsync(b).thenApplyAsync(c);
的行为与上述完全相同。
a.thenApply(b); a.thenApply(c);
表示 a
结束,然后 b
或 c
可以开始,顺序不限。 b
和 c
不必互相等待。
a.thenApplyAync(b); a.thenApplyAsync(c);
工作方式相同,就顺序而言。
在阅读以下内容之前,您应该了解以上内容。以上内容涉及异步编程,没有它您将无法正确使用 API。以下内容涉及线程管理,您可以使用它来优化您的程序并避免性能缺陷。但是如果不正确编写程序就无法优化程序。
如题:JavaCompletableFuture的thenApply
和thenApplyAsync
的区别?
我必须指出,编写 JSR 的人一定混淆了“异步编程”这个技术术语,并选择了现在让新手和老手都感到困惑的名字。首先,thenApplyAsync
中没有什么比这些方法的约定中的 thenApply
更异步的了。
两者的区别与函数在哪个线程上有关运行。提供给 thenApply
- 调用
complete
- 在同一实例上调用
thenApply
而 thenApplyAsync
的 2 个重载
- 使用默认的
Executor
(a.k.a。线程池),或者 - 使用提供的
Executor
要点是,对于 thenApply
,运行time 承诺最终 运行 您的函数会使用一些您无法控制的执行程序。如果您想控制线程,请使用异步变体。
如果您的函数是轻量级的,那么哪个线程 运行 您的函数并不重要。
如果你的函数是重 CPU 绑定,你不想把它留给 运行 时间。如果 运行 时间选择网络线程来 运行 你的函数,网络线程不能花时间处理网络请求,导致网络请求在队列中等待更长时间,你的服务器变得无响应。在这种情况下,您希望将 thenApplyAsync
与您自己的线程池一起使用。
有趣的事实:异步 != 线程
thenApply
/thenApplyAsync
,以及对应的 thenCompose
/thenComposeAsync
、handle
/handleAsync
、thenAccept
/ thenAcceptAsync
,都是异步的!这些函数的异步性质与异步操作最终 调用complete
或completeExceptionally
这一事实有关。这个想法来自 Javascript,它确实是异步的,但不是多线程的。
第二步(即计算)总是在第一步之后执行。
If the second step has to wait for the result of the first step then what is the point of Async?
Async 在这种情况下意味着您可以保证该方法 return 快速并且计算将在不同的线程中执行。
当调用 thenApply
(没有异步)时,您没有这样的保证。在这种情况下,如果在调用方法时 CompletableFuture 已经完成,则计算可以同步执行,即在调用 thenApply
的同一线程中执行。但是计算也可以由完成未来的线程或在同一 CompletableFuture
上调用方法的某个其他线程异步执行。这个答案:
那么什么时候应该使用 thenApply
什么时候使用 thenApplyAsync
?我使用以下经验法则:
- 非异步:仅当任务非常小且非阻塞时,因为在这种情况下我们不关心哪个可能的线程执行它
- async(通常带有显式执行器作为参数):用于所有其他任务