St.*_*rio 21 java multithreading forkjoinpool completable-future
我有一个关于CompletableFuture方法的问题:
public <U> CompletableFuture<U> thenApply(Function<? super T, ? extends U> fn)
Run Code Online (Sandbox Code Playgroud)
事情是JavaDoc说的就是这样:
返回一个新的CompletionStage,当该阶段正常完成时,将使用此阶段的结果作为所提供函数的参数执行.有关特殊完成的规则,请参阅CompletionStage文档.
线程怎么样?这将在哪个线程中执行?如果未来由线程池完成怎么办?
Mik*_*bel 27
正如@nullpointer指出的那样,文档会告诉您需要了解的内容.但是,相关文本出人意料地含糊不清,此处发布的一些评论(和答案)似乎依赖于文档不支持的假设.因此,我认为将它拆开是值得的.具体来说,我们应该非常仔细地阅读这一段:
为非异步方法的依赖完成提供的动作可以由完成当前CompletableFuture的线程执行,或者由完成方法的任何其他调用者执行.
听起来很简单,但细节很明显.它似乎故意避免描述何时可以在完成线程上调用依赖完成,而不是在调用完成方法时调用thenApply
.如上所述,上述段落实际上是要求我们用假设填补空白.这是危险的,特别是当主题涉及并发和异步编程时,我们作为程序员开发的许多期望开始转向他们的头脑.让我们仔细看一下文档没有说的内容.
该文档未声明在调用之前注册的依赖完成complete()
将在完成线程上运行.此外,虽然它声明在调用完成方法时可能会调用依赖完成thenApply
,但它并未声明将在注册它的线程上调用完成(请注意单词"any other").
对于任何CompletableFuture
习惯于安排和撰写任务的人来说,这些都是潜在的重点.考虑这一系列事件:
f.thenApply(c1)
.f.complete()
.f.thenApply(c2)
.从概念上讲,complete()
做两件事:它发布未来的结果,然后它尝试调用依赖的完成.现在,如果线程C 在结果值发布后运行,但在线程B转向调用之前会发生c1
什么?根据实现,线程C可能会看到f
已完成,然后可以调用c1
和 c2
.或者,线程C可以c2
在保持线程B 调用的同时调用c1
.文档不排除这两种可能性.考虑到这一点,以下是文档不支持的假设:
c
注册的依赖完成;f
f.complete()
c
将在时间f.complete()
返回时完成;f
将在完成之后 注册的f
完成之前调用.考虑另一个例子:
f.complete()
;f.thenApply(c1)
;f.thenApply(c2)
.如果已经知道它f
已经运行完成,那么可能会假设c1
将在期间调用f.thenApply(c1)
并且c2
将在调用期间调用f.thenApply(c2)
.人们可能会进一步假设,c1
时间f.thenApply(c1)
返回将会完成.但是,文档不支持这些假设.这是可能的一个线程调用的thenApply
最终调用既 c1
和c2
,而其他线程调用都不是.
仔细分析JDK代码可以确定上述假设场景如何发挥作用.但即使这样也存在风险,因为您最终可能依赖于(1)不可移植或(2)可能发生变化的实施细节.你最好的选择是不要假设javadocs或原始JSR规范中没有说明的任何内容.
tldr:小心你的假设,当你写文档时,要尽可能清楚和慎重.虽然简洁是一件很美妙的事情,但要注意填补空白的人类倾向.
Nam*_*man 20
CompletableFuture
文档中指定的策略可以帮助您更好地理解:
为非异步方法的依赖完成提供的动作可以由完成当前CompletableFuture的线程执行, 或者由完成方法的任何其他调用者执行.
所有没有显式Executor参数的异步方法都是使用
ForkJoinPool.commonPool()
(除非它不支持 至少两个并行级别,在这种情况下,创建一个新的Thread来运行每个任务).为了简化监视,调试和跟踪,所有生成的异步任务都是标记接口的实例CompletableFuture.AsynchronousCompletionTask
.
更新:我还建议@Mike阅读这个答案,作为对文档细节的进一步有趣分析.
当涉及到线程时,缺少 API 文档。理解线程和期货是如何工作的需要一些推理。从一个假设开始: 的非Async
方法CompletableFuture
不会自行产生新线程。工作将在现有线程下进行。
thenApply
将在原始CompletableFuture
线程中运行。那要么是调用 的线程,要么是在未来已经完成的情况下complete()
调用的线程thenApply()
。如果你想控制线程——如果fn
是一个缓慢的操作是个好主意——那么你应该使用thenApplyAsync
.