ForkJoinPool 和普通 ExecutionService 的区别?

Difference between ForkJoinPool and normal ExecutionService?

我读了一篇关于 fork-join framework in Java 7 的很棒的文章,其想法是,使用 ForkJoinPoolForkJoinTask,池中的线程可以从其他任务中获取子任务, 因此它能够使用更少的线程来处理更多的任务。

然后我尝试使用普通的ExecutorService做同样的工作,发现我无法区分,因为当我向池中提交一个新任务时,任务将是运行 在另一个可用线程上。

我能说的唯一区别是如果我使用 ForkJoinPool,我不需要将池传递给任务,因为我可以调用 task.fork() 使它成为 运行在另一个线程上。但是对于正常的 ExecutorService,我必须将池传递给任务,或者使其成为静态的,所以在任务内部,我可以调用 pool.submit(newTask)

我错过了什么吗?

(可以查看https://github.com/freewind/fork-join-test/tree/master/src的直播代码)

虽然 ForkJoinPool 实现了 ExecutorService,但它在概念上不同于 'normal' 执行器。

如果您的任务产生更多任务并等待它们完成,您可以很容易地看出差异,例如通过调用

executor.invoke(new Task()); // blocks this thread until new task completes

在普通的执行器服务中,等待其他任务完成会阻塞当前线程。有两种可能的结果:如果您的执行程序服务具有 固定数量的线程 ,如果最后一个 运行 线程等待另一个任务完成,它可能会死锁。如果您的执行程序按需动态创建新线程,线程数量可能会激增,您最终会拥有数千个线程,这可能会导致饥饿。

相反,fork/join框架在执行其他任务的同时重用了线程,所以即使线程数固定也不会死锁:

new MyForkJoinTask().invoke();

因此,如果您遇到可以递归解决的问题,请考虑使用 ForkJoinPool,因为您可以轻松实现一级递归,如 ForkJoinTask.

只需检查示例中 运行 个线程的数量。