18

我读到了 Java 7 中引入的 Fork/Join 框架的实现,我只是想检查一下我是否理解这个魔法是如何工作的。

据我了解,当一个线程分叉时,它会在其队列中创建子任务(其他线程可能会或可能不会窃取)。当线程尝试“加入”时,它实际上会检查其队列中的现有任务,然后递归地执行它们,这意味着对于任何“加入”操作 - 2 帧将被添加到线程调用堆栈中(一个用于加入,一个用于对于新的任务调用)。

据我所知,JVM 不支持尾调用优化(在这种情况下可能用于删除 join 方法堆栈帧),我相信在执行具有大量分叉和连接的复杂操作时,线程可能会抛出StackOverflowError.

我是对的还是他们找到了一些很酷的方法来防止它?

编辑

这是一个有助于澄清问题的场景:假设(为简单起见)我们在 forkjoin 池中只有一个线程。在某个时间点 - 线程分叉,然后调用 join。在 join 方法中,线程发现它可以执行分叉的任务(正如它在其队列中发现的那样),因此它调用下一个任务。该任务依次分叉,然后调用 join - 因此在执行 join 方法时,线程将在其队列中找到分叉的任务(如前所述)并调用它。在那个阶段,调用堆栈将至少包含两个连接和两个任务的帧。

如您所见,fork join 框架已转换为普通递归。因为 java 不支持尾调用优化 - java 中的每个递归都可能导致StackOverflowError如果它足够深。

我的问题是——fork/join 框架的实现者是否找到了一些很酷的方法来防止这种情况。

4

3 回答 3

8

不幸的是,就线程递归堆栈而言,没有什么神奇的事情发生。如果您的初始任务分叉/拆分并且没有合理的解决点,那么您将遇到 StackOverflowErrors。

您可能会理解为什么 JavaDoc 上的教程将每个子任务分成两半。

于 2012-07-05T21:26:04.150 回答
2

一般来说,压入堆栈的每个新任务的大小都是前一个任务的一半。因此,工作量随着堆栈大小呈指数增长。即使只有很小的堆栈,您也将能够完成足够多的工作来让您忙碌一段时间。

于 2012-06-29T14:15:08.693 回答
1

我希望我能以正确的方式理解你。

forkjoinpool 中有一个内部队列,用于保存您要执行的任务,因此不会引发堆栈溢出,但您必须为高内存利用率做好准备。

fork 方法非常有趣的地方是 ForkJoinWorkerThread.pushTask 使用不安全的对象,所以你应该注意数组是用来存储任务的。

编辑:首先也是简单的 - 当你在队列的顶部时,你只是简单地取消并执行,并返回 retult 。(forkjointask.java:353)

当您有依赖项时使用不同的方法,在这种情况下,控制权返回给 WorkerThread,然后由后者负责检测链并执行它们。通过第一个工作人员检查本地队列中是否有任何未完成的任务,如果没有此类任务,则执行传递的作业并返回结果,否则进入下一个案例。这多次帮助盗窃者。没有什么能帮上忙...第一步等于 MAX_HELP 的重试现在为零 - 控制权被传递给池,池执行多项检查并执行 tryAwaitDone。并在此方法中调用 wait 以等待任务完成。

这意味着分叉连接池将分几个步骤完成,试图通过避免调用等待来优化速度和时间。但是它可能会在等待中完成,那么这意味着要启动同步过程,这是非常昂贵的。

因此,没有无限深度的后续连接,而是尽可能快地执行任务的逻辑尝试。

于 2012-07-05T17:53:37.183 回答