介绍

通常,实现一个简单的并发Java应用程序时,您实现某些 Runnable 对象和对应的 Thread 对象。 您在程序中控制线程的创建、执行及状态。 Java 5对此有改进,提供了 Executor 和 ExecutorService 接口及实现这些接口的类(如 ThreadPoolExecutor 类)

Executor 框架分离了任务的创建和执行。只需要实现 Runnable 对象并使用一个 Executor 对象。 将 Runnable 任务提交给 executor,executor 将创建、管理并结束必要的线程来执行那些任务。

Java 7更进一步,另外包含了面向一个特定类型问题的 ExecutorService 的一个实现。就是 Fork/Join 框架。

此框架设计是为了解决那些可以被分治技术分解为小任务的问题。 在一个任务内,您检查要解决的问题的规模,如果大于一个确定的大小,您将其分解为用此框架执行的更小的任务。 如果问题规模小过确定的大小,您在任务里直接解决,然后框架返回或不返回一个结果。

    Task 1
        |-分解 -> Task 1.1
        |           |-分解 -> Task 1.1.1
        |           |-分解 -> Task 1.1.2
        |-分解 -> Task 1.2
                    |-分解 ->Task 1.2.1
                    |-分解 ->Task 1.2.2

决定任务是否要被分解的参考规模是没有公式来计算的。您可以使用任务中要处理的元素数目和预计的执行时间来决定参考规模。 试验不同的参考规模来选择对问题最合适的大小。可将 ForkJoinPool 看作一种特殊的 Executor。

此框架基于以下2个操作:

  • fork : 将任务分成更小的任务并用此框架执行
  • join : 任务等待它创建的任务执行完

Fork/Join 和 Executor 框架之间的主要区别在于 工作窃取(work-stealing) 算法。 当一个任务在用join操作等待它创建的子任务结束时,在执行该任务的线程(工作线程)查找还没有执行的其他任务并开始执行。 通过这种方式,线程充分利用了运行时间,因此提高了应用程序的性能。

为达到此目的,Fork/Join 框架执行的任务有以下限制:

  • 任务只能使用 fork() 和 join() 操作作为同步机制。 当任务使用其他的同步机制,工作线程不能执行其他的处于同步操作中的任务。 例如:如果您在 Fork/Join 框架里让一个任务睡眠,执行此任务的工作线程在睡眠期间不会执行其他任务。
  • 任务不应当执行 I/O 操作,例如从一个文件读、写数据
  • 任务不能抛出非运行时异常。必须有必要的代码处理好异常。

For/Join 框架的核心由以下2个类组成:

  • ForkJoinPool: 实现了 ExecutorService 接口和工作窃取算法。 它管理工作线程并提供关于任务状态和执行的信息。
  • ForkJoinTask: 将在 ForkJoinPool 中执行的任务的基类。 它提供了在一个任务中执行 fork() 和 join() 操作的机制,控制任务状态的方法。 一般为了实现 Fork/Join 任务,您将实现 RecursiveAction 类的 2 个子类(一个返回结果一个不返回)的子类。