我读到了Java 7中引入的Fork / Join框架的实现,我只是想检查一下我是否理解了魔法是如何工作的。
据我所知,当一个线程分叉时,它会在其队列中创建子任务(其他线程可能会或可能不会窃取)。当线程尝试“加入”时,它实际上检查其队列中的现有任务,然后递归执行它们,这意味着对于任何“连接”操作 - 将在线程调用堆栈中添加2个帧(一个用于连接,一个用于连接)对于新的任务调用)。
我知道JVM不支持尾调用优化(可能在这种情况下用于删除连接方法堆栈帧)我相信在执行带有大量分支和连接的复杂操作时,线程可能会抛出StackOverflowError
。
我是对的还是他们找到了一些防止它的好方法?
编辑
以下是帮助澄清问题的方案: 说(为简单起见)我们在forkjoin池中只有一个线程。 在某个时间点 - 线程分叉然后调用join。在join方法中,线程发现它可以执行分叉任务(因为它在队列中找到),因此它调用下一个任务。此任务依次分叉然后调用join - 因此在执行join方法时,线程将在其队列中找到分叉任务(如前所述)并调用它。 在该阶段,调用堆栈将至少包含两个连接和两个任务的帧。
你可以看到fork join框架转换为普通递归。因为java不支持尾部调用优化 - 如果它足够深入,java中的每个递归都会导致StackOverflowError
。
我的问题是 - fork / join框架的实现者是否找到了防止这种情况的一些很酷的方法。
答案 0 :(得分:8)
遗憾的是,就线程递归堆栈而言,没有任何神奇的事情发生。如果您的初始任务分叉/分裂并且没有合理的分辨率点,那么您将遇到StackOverflowErrors。
您可能理解为什么JavaDoc上的教程将每个子任务分成两半。
答案 1 :(得分:2)
通常,在堆栈上推送的每个新任务都是前一个任务的一半。因此,工作量随着堆栈大小呈指数增长。即使只有很小的堆叠,你也可以适应足够的工作来让你忙碌一段时间。
答案 2 :(得分:1)
我希望我能以正确的方式理解你。
forkjoinpool中有内部队列来保存您要执行的任务,因此不会抛出堆栈溢出,但您必须为高内存利用率做好准备。
fork方法非常有趣的地方是ForkJoinWorkerThread.pushTask,使用不安全的对象,所以你应该注意数组用于存储任务。
编辑: 首先也很简单 - 当您处于队列的顶部时,您只需按下并执行,然后返回retult。 (forkjointask.java:353)
当您有依赖项时使用不同的方法,在这种情况下,控制权返回给WorkerThread,然后由负责检测链并执行它们。 第一个工作人员检查本地队列是否有任何未完成的任务,如果没有这样的任务,它执行传递的作业并返回结果,否则进入下一个案例。 这几次帮助了偷窃者。 没有什么可以帮助......第一步的重试等于MAX_HELP现在为零 - 控制被传递给池,它执行多次检查并执行tryAwaitDone。 在这种方法中,调用wait来等待任务完成。
这意味着fork join pool将分几步完成,尝试通过避免等待调用来优化速度和时间。然而,它可以等待完成,然后这意味着启动同步过程非常昂贵。
所以没有后续的连接以获得无限深度,但是逻辑尝试尽可能快地执行任务。