我对CompletableFuture方法有疑问:
public <U> CompletableFuture<U> thenApply(Function<? super T, ? extends U> fn)
事情是JavaDoc这么说的:
返回一个新的CompletionStage,当此阶段正常完成时,将使用该阶段的结果作为所提供函数的参数来执行该阶段。有关涵盖异常完成的规则,请参见CompletionStage文档。
那线程呢?这将在哪个线程中执行?如果将来由线程池完成怎么办?
CompletableFuture文档中指定的策略可以帮助您更好地理解:
CompletableFuture
对于非异步方法依赖完井供给动作可以由执行 该完成当前CompletableFuture线程 , 或者通过完成方法的任何其他呼叫者 。 所有没有显式Executor参数的异步方法都使用来执行ForkJoinPool.commonPool()(除非它不支持 并行度至少为2,在这种情况下,将创建一个新的Thread来运行每个任务 )。为了简化监视,调试和跟踪,所有生成的异步任务都是标记接口的实例CompletableFuture.AsynchronousCompletionTask。
对于非异步方法依赖完井供给动作可以由执行 该完成当前CompletableFuture线程 , 或者通过完成方法的任何其他呼叫者 。
所有没有显式Executor参数的异步方法都使用来执行ForkJoinPool.commonPool()(除非它不支持 并行度至少为2,在这种情况下,将创建一个新的Thread来运行每个任务 )。为了简化监视,调试和跟踪,所有生成的异步任务都是标记接口的实例CompletableFuture.AsynchronousCompletionTask。
ForkJoinPool.commonPool()
CompletableFuture.AsynchronousCompletionTask
更新 :我也建议阅读@Mike的答案,作为对文档详细信息的有趣分析。
正如@nullpointer指出的那样,文档会告诉您您需要了解的内容。但是,相关文本令人惊讶地模糊不清,此处发布的某些评论(和答案)似乎依赖于文档不支持的假设。因此,我认为有必要将其分开。具体来说,我们应该非常仔细地阅读本段内容:
为非异步方法的相关完成提供的动作可以由完成当前CompletableFuture的线程执行,也可以由完成方法的任何其他调用者执行。
听起来很简单,但是细节上却很少。似乎故意避免描述何时可以在完成线程上调用依赖完成,而不是在调用诸如的完成方法期间thenApply。如前所述,以上段落实际上是在恳求我们用假设来填补空白。这很危险,特别是当主题涉及并发和异步编程时,当我们随着程序员的发展而产生的许多期望被抛诸脑后。让我们仔细看一下文档中没有说的内容。
thenApply
该文档没有声称在调用之前注册的相关完成complete()将在完成线程上运行。而且,尽管它声明在调用诸如的完成方法时可能会调用从属补全thenApply,但并未声明将在注册它的线程上调用补全(注意单词“ any other”)。
complete()
“ 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()
考虑另一个示例:
如果知道f已经完成,可以尝试假设c1会在期间调用f.thenApply(c1)和c2会在期间调用f.thenApply(c2)。人们可能会进一步假设,c1到时间f.thenApply(c1)返回时,它已经完成了。但是,文档不支持这些假设。它可能是一个线程,呼吁thenApply各上调调用既 c1和c2,而其他线程调用都不是。
仔细分析JDK代码可以确定上面的假设方案如何发挥作用。但这甚至是有风险的,因为您最终可能会依赖于(1)不可移植或(2)可能会更改的实现细节。最好的选择是不要假设javadocs或原始JSR规范中未阐明的任何内容。
tldr:谨慎假设,编写文档时,请尽可能清晰明了。简洁是一件奇妙的事情,但请注意人类填补空白的趋势。