[英]In which thread do CompletableFuture's completion handlers execute?
I have a question about CompletableFuture method:我对 CompletableFuture 方法有疑问:
public <U> CompletableFuture<U> thenApply(Function<? super T, ? extends U> fn)
The thing is the JavaDoc says just this:问题是 JavaDoc 就是这样说的:
Returns a new CompletionStage that, when this stage completes normally, is executed with this stage's result as the argument to the supplied function.返回一个新的 CompletionStage,当此阶段正常完成时,将使用此阶段的结果作为所提供函数的参数来执行该阶段。 See the CompletionStage documentation for rules covering exceptional completion.有关异常完成的规则,请参阅 CompletionStage 文档。
What about threading?线程呢? In which thread is this going to be executed?这将在哪个线程中执行? What if the future is completed by a thread pool?如果future是由一个线程池完成的呢?
As @nullpointer points out, the documentation tells you what you need to know. 正如@nullpointer指出的那样,文档会告诉您您需要了解的内容。 However, the relevant text is surprisingly vague, and some of the comments (and answers) posted here seem to rely on assumptions that aren't supported by the documentation. 但是,相关文本令人惊讶地含糊,此处发布的某些评论(和答案)似乎依赖于文档不支持的假设。 Thus, I think it's worthwhile to pick it apart. 因此,我认为有必要将其分开。 Specifically, we should read this paragraph very carefully: 具体来说,我们应该非常仔细地阅读本段内容:
Actions supplied for dependent completions of non-async methods may be performed by the thread that completes the current CompletableFuture, or by any other caller of a completion method. 为非异步方法的相关完成提供的动作可以由完成当前CompletableFuture的线程执行,也可以由完成方法的任何其他调用者执行。
Sounds straightforward enough, but it's light on details. 听起来很简单,但是细节上却很少。 It seemingly deliberately avoids describing when a dependent completion may be invoked on the completing thread versus during a call to a completion method like thenApply
. 似乎故意避免描述何时可以在完成线程上调用依赖完成,而不是在调用诸如thenApply
这样的完成方法thenApply
。 As written, the paragraph above is practically begging us to fill in the gaps with assumptions. 如前所述,以上段落实际上是在恳求我们用假设来填补空白。 That's dangerous, especially when the topic concerns concurrent and asynchronous programming, where many of the expectations we've developed as programmers get turned on their head. 这很危险,尤其是当主题涉及并发和异步编程时,在这种情况下,随着程序员的发展,我们已经产生了许多期望。 Let's take a careful look at what the documentation doesn't say. 让我们仔细看一下文档中没有说的内容。
The documentation does not claim that dependent completions registered before a call to complete()
will run on the completing thread. 该文档没有声明在调用complete()
之前注册的相关完成将在完成线程上运行。 Moreover, while it states that a dependent completion might be invoked when calling a completion method like thenApply
, it does not state that a completion will be invoked on the thread that registers it (note the words "any other"). 此外,尽管它声明当调用诸如thenApply
类的完成方法时可能会调用从属thenApply
,但并未声明将在注册它的线程上调用完成(请注意“其他”)。
These are potentially important points for anyone using CompletableFuture
to schedule and compose tasks. 对于使用CompletableFuture
计划和编写任务的任何人来说,这些都是潜在的重要点。 Consider this sequence of events: 考虑以下事件序列:
f.thenApply(c1)
. 线程A通过f.thenApply(c1)
注册依赖完成。 f.complete()
. 一段时间后,线程B调用f.complete()
。 f.thenApply(c2)
. 大约在同一时间,线程C通过f.thenApply(c2)
注册另一个依赖完成。 Conceptually, complete()
does two things: it publishes the result of the future, and then it attempts to invoke dependent completions. 从概念上讲, complete()
做两件事:发布将来的结果,然后尝试调用相关的完成。 Now, what happens if Thread C runs after the result value is posted, but before Thread B gets around to invoking c1
? 现在,如果线程C 在发布结果值之后但在线程B开始调用c1
之前运行,该怎么办? Depending on the implementation, Thread C may see that f
has completed, and it may then invoke c1
and c2
. 根据实现的不同,线程C可能会看到f
已经完成,然后可以调用c1
和 c2
。 Alternatively, Thread C may invoke c2
while leaving Thread B to invoke c1
. 或者,线程C可以调用c2
而线程B可以调用c1
。 The documentation does not rule out either possibility. 该文档不排除任何可能性。 With that in mind, here are assumptions that are not supported by the documentation: 考虑到这一点,以下是文档不支持的假设:
c
registered on f
prior to completion will be invoked during the call to f.complete()
; 在对f.complete()
调用期间,将在完成之前在f
注册的从属完成c
被调用; c
will have run to completion by the time f.complete()
returns; 到f.complete()
返回时, c
将运行完成。 f
completes will be invoked before completions registered after f
completes. 之前注册的依赖完井f
完成后,将注册落成之前被调用f
完成。 Consider another example: 考虑另一个示例:
f.complete()
; 线程A调用f.complete()
; f.thenApply(c1)
; 一段时间后,线程B通过f.thenApply(c1)
注册完成。 f.thenApply(c2)
. 大约在同一时间,线程C通过f.thenApply(c2)
注册一个单独的完成。 If it is known that f
has already run to completion, one might be tempted to assume that c1
will be invoked during f.thenApply(c1)
and that c2
will be invoked during f.thenApply(c2)
. 如果知道f
已经运行完毕,则可能会倾向于假设c1
将在f.thenApply(c1)
期间被调用,而c2
将在f.thenApply(c2)
期间被调用。 One might further assume that c1
will have run to completion by the time f.thenApply(c1)
returns. 可能还假定c1
将在f.thenApply(c1)
返回时运行完毕。 However, the documentation does not support these assumptions. 但是,文档不支持这些假设。 It may be possible that one of the threads calling thenApply
ends up invoking both c1
and c2
, while the other thread invokes neither. 这可能是可能的线程调用的一个 thenApply
最终调用两个 c1
和c2
,而其他线程调用都不是。
A careful analysis of the JDK code could determine how the hypothetical scenarios above might play out. 对JDK代码的仔细分析可以确定上面的假设方案如何发挥作用。 But even that is risky, because you may end up relying on an implementation detail that is (1) not portable, or (2) subject to change. 但这甚至是有风险的,因为您最终可能会依赖于(1)不可移植或(2)随时可能更改的实现细节。 Your best bet is not to assume anything that's not spelled out in the javadocs or the original JSR spec. 最好的选择是不要假设javadocs或原始JSR规范中未阐明的任何内容。
tldr: Be careful what you assume, and when you write documentation, be as clear and deliberate as possible. tldr:谨慎假设,编写文档时,请尽可能清晰明了。 While brevity is a wonderful thing, be wary of the human tendency to fill in the gaps. 简洁是一件奇妙的事情,但请注意人类填补空白的趋势。
The policies as specified in the CompletableFuture
docs could help you understand better: CompletableFuture
文档中指定的策略可以帮助您更好地理解:
Actions supplied for dependent completions of non-async methods may be performed by the thread that completes the current CompletableFuture , or by any other caller of a completion method . 为非异步方法的相关完成提供的动作可以由完成当前CompletableFuture的线程执行,也可以由完成方法的任何其他调用者执行 。
All async methods without an explicit Executor argument are performed using the
ForkJoinPool.commonPool()
(unless it does not support a parallelism level of at least two, in which case, a new Thread is created to run each task ). 所有没有显式Executor参数的异步方法都是使用ForkJoinPool.commonPool()
执行的(除非它不支持并行度至少为2,在这种情况下,将创建一个新的Thread来运行每个任务 )。 To simplify monitoring, debugging, and tracking, all generated asynchronous tasks are instances of the marker interfaceCompletableFuture.AsynchronousCompletionTask
. 为了简化监视,调试和跟踪,所有生成的异步任务都是标记接口CompletableFuture.AsynchronousCompletionTask
实例。
Update : I would also advice on reading this answer by @Mike as an interesting analysis further into the details of the documentation. 更新 :我也建议阅读@Mike的答案 ,作为对文档详细信息的有趣分析。
Actions supplied for dependent completions of non-async methods may be performed by the thread that completes the current CompletableFuture, or by any other caller of a completion method. 为非异步方法的相关完成提供的动作可以由完成当前CompletableFuture的线程执行,也可以由完成方法的任何其他调用者执行。
More concretely: 更具体地说:
fn
will run during the call to complete()
in the context of whichever thread has called complete()
. fn
将在调用complete()
的上下文中运行,无论哪个线程调用了complete()
。
If complete()
has already finished by the time thenApply()
is called, fn
will be run in the context of the thread calling thenApply()
. 如果complete()
已经由时间完成thenApply()
被调用时, fn
将在线程中调用的上下文中运行thenApply()
When it comes to threading the API documentation is lacking. 当涉及线程时,缺少API文档。 It takes a bit of inference to understand how threading and futures work. 要了解线程和期货的工作方式需要一点推论。 Start with one assumption: the non- Async
methods of CompletableFuture
do not spawn new threads on their own. 从一个假设开始: CompletableFuture
的非Async
方法不会自行产生新线程。 Work will proceed under existing threads. 工作将在现有线程下进行。
thenApply
will run in the original CompletableFuture
's thread. thenApply
将在原始CompletableFuture
的线程中运行。 That's either the thread that calls complete()
, or the one that calls thenApply()
if the future is already completed. 如果将来已经完成,那要么是调用complete()
的线程,要么是调用thenApply()
的线程。 If you want control over the thread—a good idea if fn
is a slow operation—then you should use thenApplyAsync
. 如果要控制线程(如果fn
是缓慢的操作,则是个好主意),则应使用thenApplyAsync
。
I know this question is old, but I want to use source code to explain this question.我知道这个问题很老,但我想用源代码来解释这个问题。
public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {
return uniAcceptStage(null, action);
}
private CompletableFuture<Void> uniAcceptStage(Executor e,
Consumer<? super T> f) {
if (f == null) throw new NullPointerException();
Object r;
if ((r = result) != null)
return uniAcceptNow(r, e, f);
CompletableFuture<Void> d = newIncompleteFuture();
unipush(new UniAccept<T>(e, d, this, f));
return d;
}
This is the source code from java 16, and we can see, if we trigger thenAccept, we will pass a null executor service reference into our function.这是来自 java 16 的源代码,我们可以看到,如果我们触发 thenAccept,我们会将一个空的执行器服务引用传递给我们的函数。 From the 2nd function uniAcceptStage() 2nd if condition.从第二个函数 uniAcceptStage() 第二个 if 条件。 If result is not null, it will trigger uniAcceptNow()如果结果不为空,它将触发 uniAcceptNow()
if (e != null) {
e.execute(new UniAccept<T>(null, d, this, f));
} else {
@SuppressWarnings("unchecked") T t = (T) r;
f.accept(t);
d.result = NIL;
}
if executor service is null, we will use lambda function f.accept(t) to execute it.如果执行器服务为空,我们将使用 lambda 函数 f.accept(t) 来执行它。 If we are triggering this thenApply/thenAccept from main thread, it will use main thread as executing thread.如果我们从主线程触发 thenApply/thenAccept,它将使用主线程作为执行线程。
But if we cannot get previous result from last completablefuture, we will push our current UniAccept/Apply into stack by using uniPush function.但是,如果我们无法从上一个可完成的未来中获得先前的结果,我们将使用 uniPush 函数将当前的 UniAccept/Apply 推入堆栈。 And UniAccept class has tryFire() which will be triggered from our postComplete() function UniAccept 类有 tryFire() 将由我们的 postComplete() 函数触发
final void postComplete() {
/*
* On each step, variable f holds current dependents to pop
* and run. It is extended along only one path at a time,
* pushing others to avoid unbounded recursion.
*/
CompletableFuture<?> f = this; Completion h;
while ((h = f.stack) != null ||
(f != this && (h = (f = this).stack) != null)) {
CompletableFuture<?> d; Completion t;
if (STACK.compareAndSet(f, h, t = h.next)) {
if (t != null) {
if (f != this) {
pushStack(h);
continue;
}
NEXT.compareAndSet(h, t, null); // try to detach
}
f = (d = h.tryFire(NESTED)) == null ? this : d;
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.