CompletableFuture的完成处理程序在哪个线程中执行?

时间:2017-09-05 17:30:26

标签: java multithreading completable-future forkjoinpool

我对CompletableFuture方法有疑问:

public <U> CompletableFuture<U> thenApply(Function<? super T, ? extends U> fn)

事情是JavaDoc就是这样说的:

  

返回一个新的CompletionStage,当此阶段完成时   通常,以此阶段的结果作为参数执行   提供的功能。有关规则,请参阅CompletionStage文档   涵盖特殊完成。

线程怎么样?这将在哪个线程中执行?如果未来由线程池完成怎么办?

4 个答案:

答案 0 :(得分:20)

@nullpointer所述,文档会告诉您需要了解的内容。但是,相关文本出人意料地含糊不清,此处发布的一些评论(和答案)似乎依赖于文档不支持的假设。因此,我认为将它拆开是值得的。具体来说,我们应该非常仔细地阅读这一段:

  

为非异步方法的依赖完成提供的动作可以由完成当前CompletableFuture的线程执行,也可以由完成方法的任何其他调用者执行。

听起来很简单,但细节很明显。它似乎故意避免描述何时可以在完成线程上调用依赖完成,而不是在调用诸如thenApply之类的完成方法时调用。如上所述,上面的段落实际上是在乞求我们用假设填补空白。这是危险的,特别是当主题涉及并发和异步编程时,我们作为程序员开发的许多期望开始转向他们的头脑。让我们仔细看看没有说明的文档。

文档声明在调用complete()之前注册的相关完成将在完成线程上运行。此外,虽然它声明在调用thenApply之类的完成方法时可能会调用依赖完成 ,但表示将调用完成在注册它的线程上(注意“任何其他”一词)。

对于使用CompletableFuture来安排和撰写任务的任何人来说,这些都是潜在的重点。考虑这一系列事件:

  1. 线程A通过f.thenApply(c1)注册依赖完成。
  2. 一段时间后,主题B调用f.complete()
  3. 大约在同一时间,线程C通过f.thenApply(c2)注册另一个依赖完成。
  4. 从概念上讲,complete()做两件事:它发布未来的结果,然后尝试调用依赖的完成。现在,如果在发布结果值之后线程C运行会发生什么,但在线程B绕过调用c1之前会发生什么?根据实现,线程C可能会看到f已完成,然后它可能会调用c1 c2。或者,线程C可以调用c2,同时让线程B调用c1。文档不排除这两种可能性。考虑到这一点,以下是文档中不支持的假设:

    1. 在调用c期间,将调用f f.complete() 之前注册的从属完成c;
    2. f.complete()返回时,f将完成运行;
    3. 将以任何特定顺序(例如,注册顺序)调用相关的完成;
    4. f完成之前注册的完成之前,将调用在 f.complete()完成之前注册的相关完成项。
    5. 考虑另一个例子:

      1. 主题A调用f.thenApply(c1);
      2. 一段时间后,线程B通过f.thenApply(c2);
      3. 注册完成
      4. 大约在同一时间,线程C通过f注册单独的完成。
      5. 如果知道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