ForkJoinPool#awaitQuiescence实际上是如何工作的?
我有下一个实现RecursiveAction,这个类的单一目的 - 是从 0 到 9 打印,但如果可能的话,从不同的线程打印:
public class MyRecursiveAction extends RecursiveAction {
private final int num;
public MyRecursiveAction(int num) {
this.num = num;
}
@Override
protected void compute() {
if (num < 10) {
System.out.println(num);
new MyRecursiveAction(num + 1).fork();
}
}
}
我认为调用awaitQuiescence将使当前线程等待所有任务(提交和分叉)完成:
public class Main {
public static void main(String[] args) {
ForkJoinPool forkJoinPool = new ForkJoinPool();
forkJoinPool.execute(new MyRecursiveAction(0));
System.out.println(forkJoinPool.awaitQuiescence(5, TimeUnit.SECONDS) ? "tasks" : "time");
}
}
但我并不总是得到正确的结果,而不是打印 10 次,打印 0 到 10 次。
但是,如果我添加helpQuiesce到我的实现中RecursiveAction:
public class MyRecursiveAction extends RecursiveAction {
private final int num;
public MyRecursiveAction(int num) {
this.num = num;
}
@Override
protected void compute() {
if (num < 10) {
System.out.println(num);
new MyRecursiveAction(num + 1).fork();
}
RecursiveAction.helpQuiesce();//here
}
}
一切正常。
我想知道究竟在awaitQuiescence等待什么?
回答
当你改变你得到的会发生什么的想法System.out.println(num);来System.out.println(num + " " + Thread.currentThread());
这可能会打印如下内容:
0 Thread[ForkJoinPool-1-worker-3,5,main]
1 Thread[main,5,main]
tasks
2 Thread[ForkJoinPool.commonPool-worker-3,5,main]
当awaitQuiescence检测到有待处理的任务时,它会通过窃取一个并直接执行来提供帮助。它的文档说:
如果由在此池中运行的 ForkJoinTask 调用,则等效于 ForkJoinTask.helpQuiesce()。否则,等待和/或尝试协助执行任务,直到此池 isQuiescent() 或指示的超时过去。
重点是我加的
这发生在这里,我们可以看到,一个任务打印“main”作为它的执行线程。然后, 的行为fork()被指定为:
安排在当前任务正在运行的池中异步执行此任务(如果适用),或使用
ForkJoinPool.commonPool()if notinForkJoinPool()。
由于该main线程不是 a 的工作线程ForkJoinPool,所以fork()会将新任务提交给commonPool()。从那时起,fork()从公共池的工作线程调用的 将提交下一个任务到公共池。但是awaitQuiescence在自定义池上调用不会等待公共池的任务完成并且 JVM 过早终止。
如果你要说这是一个有缺陷的 API 设计,我不会反对。
解决方案不是awaitQuiescence用于公共池¹。通常,RecursiveAction拆分子任务的 a 应该等待它们完成。然后,您可以等待根任务完成以等待所有关联任务完成。
此答案的后半部分包含此类RecursiveAction实现的示例。
¹awaitQuiescence当您没有实际的期货时很有用,例如提交到公共池的并行流。