我对CompletableFuture方法有疑问:
public <U> CompletableFuture<U> thenApply(Function<? super T, ? extends U> fn)
事情是JavaDoc就是这样说的:
返回一个新的CompletionStage,当此阶段完成时 通常,以此阶段的结果作为参数执行 提供的功能。有关规则,请参阅CompletionStage文档 涵盖特殊完成。
线程怎么样?这将在哪个线程中执行?如果未来由线程池完成怎么办?
答案 0 :(得分:20)
如@nullpointer所述,文档会告诉您需要了解的内容。但是,相关文本出人意料地含糊不清,此处发布的一些评论(和答案)似乎依赖于文档不支持的假设。因此,我认为将它拆开是值得的。具体来说,我们应该非常仔细地阅读这一段:
为非异步方法的依赖完成提供的动作可以由完成当前CompletableFuture的线程执行,也可以由完成方法的任何其他调用者执行。
听起来很简单,但细节很明显。它似乎故意避免描述何时可以在完成线程上调用依赖完成,而不是在调用诸如thenApply
之类的完成方法时调用。如上所述,上面的段落实际上是在乞求我们用假设填补空白。这是危险的,特别是当主题涉及并发和异步编程时,我们作为程序员开发的许多期望开始转向他们的头脑。让我们仔细看看没有说明的文档。
文档不声明在调用complete()
之前注册的相关完成将在完成线程上运行。此外,虽然它声明在调用thenApply
之类的完成方法时可能会调用依赖完成 ,但不表示将调用完成在注册它的线程上(注意“任何其他”一词)。
对于使用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
和{{1}},而另一个线程调用两者都不是。
仔细分析JDK代码可以确定上述假设情景如何发挥作用。但即使这样也存在风险,因为您最终可能依赖于(1)不可移植或(2)可能发生变化的实施细节。你最好的选择是不要假设javadocs或原始JSR规范中没有说明的任何内容。
tldr:请注意您的假设,并在撰写文档时尽可能清晰和慎重。虽然简洁是一件很美妙的事情,但要注意填补空白的人类倾向。
答案 1 :(得分:18)
CompletableFuture
文档中指定的政策可以帮助您更好地理解:
为非异步方法的依赖完成提供的操作可能是 由完成当前CompletableFuture 的线程执行, 或完成方法的任何其他来电者。
执行所有没有显式Executor参数的异步方法 使用
ForkJoinPool.commonPool()
(除非它不支持 并行度至少为2,在这种情况下,新的Thread是 创建用于运行每个任务)。简化监视,调试和 跟踪,所有生成的异步任务都是标记的实例 界面CompletableFuture.AsynchronousCompletionTask
。
更新 :我还建议@Mike阅读this answer,作为对文档细节的进一步有趣分析。
答案 2 :(得分:6)
来自Javadoc:
为非异步方法的依赖完成提供的动作可以由完成当前CompletableFuture的线程执行,也可以由完成方法的任何其他调用者执行。
更具体地说:
fn
将在调用complete()
的任何线程调用complete()
的情况下运行。
如果complete()
在调用thenApply()
时已经完成,fn
将在调用thenApply()
的线程的上下文中运行。
答案 3 :(得分:3)
当涉及到线程时,缺乏API文档。理解线程和期货是如何工作需要一些推论。从一个假设开始:Async
的非CompletableFuture
方法不会自己生成新线程。工作将在现有线程下进行。
thenApply
将在原始CompletableFuture
的主题中运行。这是调用complete()
的线程,或者如果未来已经完成则调用thenApply()
的线程。如果你想控制线程 - 如果fn
是一个缓慢的操作,那么你应该使用thenApplyAsync
。