- Fork/Join 框架是 JDK 1.7 提供的并行执行任务框架,这个框架通过(递归)把问题划分为子任务,然后并行的执行这些子任务,等所有的子任务都结束的时候,再合并最终结果的这种方式来支持并行计算编程。
- 总体的设计参考了为 Cilk 设计的 work-stealing 框架。
- Fork/Join 并行方式是获取良好的并行计算性能的一种最简单同时也是最有效的设计技术,是 分治算法(Divide-and-Conquer) 的并行版本。
- Fork 操作启动一个新的并行 Fork/Join 子任务。
- Join 操作一直等待直到所有的子任务都结束。
- Fork/Join 算法,如同其他分治算法一样,总是会递归的、反复的划分子任务,直到这些子任务可以用足够简单的、短小的顺序方法来执行。
fork/join框架是ExecutorService
接口的一种具体实现,目的是为了帮助你更好地利用多处理器带来的好处。它是为那些能够被递归地拆解成子任务的工作类型量身设计的。其目的在于能够使用所有可用的运算能力来提升你的应用的性能。
类似于ExecutorService
接口的其他实现,fork/join框架会将任务分发给线程池中的工作线程。fork/join框架的独特之处在与它使用工作窃取(work-stealing)算法。完成自己的工作而处于空闲的工作线程能够从其他仍然处于忙碌(busy)状态的工作线程处窃取等待执行的任务。
fork/join框架的核心是ForkJoinPool
类,它是对AbstractExecutorService
类的扩展。ForkJoinPool
实现了工作偷取算法,并可以执行ForkJoinTask
任务。
基本使用方法
使用fork/join框架的第一步是编写执行一部分工作的代码。你的代码结构看起来应该与下面所示的伪代码类似:
if (当前这个任务工作量足够小)
直接完成这个任务
else
将这个任务或这部分工作分解成两个部分
分别触发(invoke)这两个子任务的执行,并等待结果
你需要将这段代码包裹在一个ForkJoinTask
的子类中。不过,通常情况下会使用一种更为具体的的类型,或者是RecursiveTask
(会返回一个结果),或者是RecursiveAction
。
当你的ForkJoinTask
子类准备好了,创建一个代表所有需要完成工作的对象,然后将其作为参数传递给一个ForkJoinPool
实例的invoke()
方法即可。
2. 工作窃取(work-stealing)
- ForkJoin 框架的核心在于轻量级调度机制,使用了 工作窃取(Work-Stealing)所采用的基本调度策略。
work-stealing
- 每一个工作线程维护自己的调度队列中的可运行任务。
- 队列以双端队列的形式被维护。
- 支持后进先出 LIFO 的 push 和 pop 操作。
- 支持先进先出 FIFO 的 take 操作。
- 对于一个给定的工作线程来说,任务所产生的子任务将会被放入到工作者自己的双端队列中。
- 工作线程使用后进先出 LIFO(最新的元素优先)的顺序,通过弹出任务来处理队列中的任务。
- 当一个工作线程的本地没有任务去运行的时候,它将使用先进先出 FIFO 的规则尝试随机的从别的工作线程中拿(窃取)一个任务去运行。
- 当一个工作线程触及了 Join 操作,如果可能的话它将处理其他任务,直到目标任务被告知已经结束(通过
isDone()
方法)。所有的任务都会 无阻塞 的完成。 - 当一个工作线程无法再从其他线程中获取任务和失败处理的时候,它就会退出并经过一段时间之后再度尝试直到所有的工作线程都被告知他们都处于空闲的状态。
- 在这种情况下,他们都会阻塞直到其他的任务再度被上层调用。
- 使用后进先出 LIFO 用来处理每个工作线程的自己任务,但是使用先进先出 FIFO 规则用于获取别的任务,这是一种被广泛使用的进行递归 Fork/Join 设计的一种调优手段。
- 让窃取任务的线程从队列拥有者相反的方向进行操作会减少线程竞争。
- 同样体现了递归分治算法的大任务优先策略。
- 更早期被窃取的任务有可能会提供一个更大的单元任务,从而使得窃取线程能够在将来进行递归分解。
- 对于一些基础的操作而言,使用相对较小粒度的任务比那些仅仅使用粗粒度划分的任务以及那些没有使用递归分解的任务的运行速度要快。尽管相关的少数任务在大多数的 Fork/Join 框架中会被其他工作线程窃取,但是创建许多组织良好的任务意味着只要有一个工作线程处于可运行的状态,那么这个任务就有可能被执行。
工作窃取算法的优点
- 利用了线程进行并行计算,减少了线程间的竞争。
工作窃取算法的缺点
- 如果双端队列中只有一个任务时,线程间会存在竞争。
- 窃取算法消耗了更多的系统资源,如会创建多个线程和多个双端队列。
3. ForkJoinPool
- ForkJoinPool 类是 Fork/Join 框架 的核心,和 ThreadPoolExecutor 一样是 ExecutorService 接口的实现类。
- ForkJoinPool 不是为了替代 ExecutorService,而是它的补充,在某些应用场景下性能比 ExecutorService 更好。(见 Java Tip: When to use ForkJoinPool vs ExecutorService )
ForkJoinPool 类图
- ForkJoinPool 的两大核心就是 分而治之(Divide-and-Conquer)和工作窃取(Work-Stealing)算法。
- ForkJoinPool 最适合的是计算密集型的任务,如果存在 I/O,线程间同步,
sleep()
等会造成线程长时间阻塞的情况时,最好配合使用 ManagedBlocker。
- ForkJoinPool 最适合的是计算密集型的任务,如果存在 I/O,线程间同步,
3.1 Fork/Join 框架的使用
- ThreadPoolExecutor 中每个任务都是由单个线程独立处理的,如果出现一个非常耗时的大任务(比