Java7 Fork/Join 核心类

在 Java7 中,Fork/Join 框架的核心类主要有以下几个:

🚩ForkJoinPool 类

ForkJoinPool 是 Java7 引入的用于并行执行任务的特殊线程池,设计用于 fork-join 任务分拆,它是实现 Fork/Join 框架的核心部分,负责管理和调度 Fork/Join 任务的执行。

ForkJoinPool 位于 java.util.concurrent 包中,因此类的全名是 java.util.concurrent.ForkJoinPool。

我们可以通过 ForkJoinPool.commonPool() 方法获取默认的通用线程池,也可以创建自定义的 ForkJoinPool。

⚠️注意:ForkJoinPool.commonPool() 方法位于 JDK 1.8,如下图:

image.png

创建 ForkJoinPool

您可以使用 ForkJoinPool 的构造函数创建一个 ForkJoinPool。作为 ForkJoinPool 构造函数的参数,您需要传递所需的并行程度。并行程度表示你希望有多少个线程或 CPU 同时处理传递给 ForkJoinPool 的任务(建议不要大于 CPU 的核心数)。下面是一个创建 ForkJoinPool 的示例:

ForkJoinPool forkJoinPool = new ForkJoinPool(4);

上例创建了一个并行度为 4 的 ForkJoinPool 示例。

将任务提交到 ForkJoinPool

向 ForkJoinPool 提交任务的方式与向 ExecutorService 提交任务的方式类似。您可以提交两种类型的任务:

  • 一种是不返回任何结果的任务("action")

  • 另一种是返回结果的任务("task")

这两种任务分别由 RecursiveAction 和 RecursiveTask 类表示,后续章节将详细介绍如何使用这两种任务以及如何提交它们。

简单示例:

ForkJoinPool forkJoinPool = new ForkJoinPool(4);
// 提交一个任务到 ForkJoinPool
forkJoinPool.execute(new ForkJoinTask<Integer>() {
    //...
});

ForkJoinPool 重要方法

  • void execute(ForkJoinTask<?> task):异步执行给定的任务,不等待其完成。

  • <T> ForkJoinTask<T> submit(ForkJoinTask<T> task):提交任务以执行,并返回一个表示该任务的ForkJoinTask。提交的任务会在某个线程中异步执行,但你可以通过返回的ForkJoinTask来查询任务的状态或等待其完成。

  • <T> T invoke(ForkJoinTask<T> task):等待任务完成,并返回其结果。如果调用线程未在等待结果时阻塞,则可能通过其他方式继续执行。

  • boolean isTerminated():如果所有任务都已完成执行,则返回true。这包括已提交的任务、正在执行的任务以及已排队但尚未开始的任务。

  • boolean awaitTermination(long timeout, TimeUnit unit):等待直到所有任务完成执行,或者等待时间超过给定的超时时间,或者线程被中断,以最先发生者为准。如果超时时间为0,则该方法将立即返回。

  • void shutdown():启动有序关闭过程,在此过程中不再接受新任务,但会等待已提交的任务完成执行。

  • void shutdownNow():尝试停止所有正在执行的任务,停止处理正在等待的任务,并返回等待执行的任务列表。

🚩ForkJoinTask 类

ForkJoinTask 是一个抽象类,表示可以在 ForkJoinPool 中执行的任务。它为并行计算提供了基础,支持任务的分割(fork)和结果的合并(join)。同时它也实现了Future接口,允许任务等待异步计算的结果。

主要特点

  • 可并行性:ForkJoinTask 旨在通过递归分解任务成更小的部分,然后并行执行这些部分来利用多核处理器的计算能力。

  • 工作窃取:ForkJoinPool(ForkJoinTask 执行的线程池)使用工作窃取算法来平衡负载,允许空闲的线程从其他线程的工作队列中窃取任务。

  • 结果合并:对于返回结果的任务(即RecursiveTask的子类),ForkJoinTask 提供了合并子任务结果的机制。

核心方法

  • fork():将任务提交给 ForkJoinPool 进行异步执行。此方法会立即返回,而不会等待任务完成。

  • join():等待任务完成并获取结果(对于 RecursiveTask)。如果当前线程不是执行该任务的线程,则调用 join() 会阻塞当前线程直到任务完成。

  • invoke():类似于 join(),但它在调用线程中直接执行任务,而不是在 ForkJoinPool 中异步执行。

  • compute():这是 RecursiveTask 和 RecursiveAction 必须实现的抽象方法。它包含了任务的计算逻辑,以及对于 RecursiveTask,还包含了如何拆分任务并合并结果的逻辑。

重要子类

RecursiveAction 类

RecursiveAction 是 Java 并发包 java.util.concurrent 中的一个类,它继承自 ForkJoinTask,主要用于执行那些不需要返回结果的任务。RecursiveAction 通过将复杂任务递归地分解为更小的子任务,并允许这些子任务并行执行,从而提高整体性能。它特别适用于那些可以分解为更小独立任务,且不需要聚合子任务结果的问题。

主要特点
  • 无返回值:与 RecursiveTask 不同,RecursiveAction 不需要返回结果。这意味着在执行过程中,它专注于任务的执行,而不是结果的收集。

  • 并行性:RecursiveAction 允许将任务分解为多个子任务,这些子任务可以并行执行,以充分利用多核处理器的计算能力。

  • 分而治之:它采用分而治之的策略,将大问题分解成小问题,直到问题变得足够小可以直接解决。

  • 简化并行编程:通过提供清晰的框架和方法,RecursiveAction 简化了并行编程的复杂性,使得开发者可以更容易地实现高效的并行计算。

使用场景

RecursiveAction 适用于以下场景:

  • 并行遍历和处理数据结构:如遍历大型数组或列表,并对每个元素执行某种操作,可以将数据结构分成几个部分并行处理。

  • 分治算法:如快速排序、归并排序等,这些算法天然地适合使用 RecursiveAction 进行并行化。

  • 图像处理:将大型图像分割成多个小块,并行地对每个小块进行处理。

  • 批处理任务:如发送批量电子邮件、并行下载文件等可并行化的批处理任务。

RecursiveTask 类

RecursiveTask 是 Java 并发包 java.util.concurrent 中的一个抽象类,它继承自 ForkJoinTask,专门用于表示那些可以递归划分并且可能需要执行大量计算的任务,且这些任务在执行完成后会返回一个结果。通过利用 ForkJoinPool(一个为执行并行任务而设计的线程池),RecursiveTask 能够将复杂任务递归地分解为更小的子任务,这些子任务可以并行执行,从而提高程序的执行效率。

主要特点
  • 有返回值:RecursiveTask 与 RecursiveAction 的主要区别在于,RecursiveTask 在任务执行完成后会返回一个结果。这使得 RecursiveTask 特别适合于需要收集子任务结果并合并成最终结果的场景。

  • 并行性:通过 ForkJoinPool,RecursiveTask 能够充分利用多核处理器的计算能力,将任务并行化执行,以加快计算速度。

  • 简化并行编程:RecursiveTask 提供了一种结构化的方式来编写并行代码,使得代码更容易理解和维护。它还提供了许多有用的工具和机制,如任务拆分、依赖管理、结果合并等,进一步简化了并行编程的复杂性。

  • 分而治之:RecursiveTask 采用分而治之的策略,将大问题分解成小问题,直到问题变得足够小可以直接解决。这种策略特别适合于解决那些可以递归分解的复杂问题。

使用场景

RecursiveTask 适用于以下场景:

  • 并行计算密集型任务:如大规模数据处理、科学计算等,这些任务可以分解为多个独立的子任务并行执行。

  • 递归分解任务:当任务可以递归地分解为更小的子任务时,RecursiveTask 能够有效地减少单个递归调用的深度,降低栈溢出的风险。

  • 需要收集结果的任务:如果任务执行完成后需要收集并合并子任务的结果,那么 RecursiveTask 是一个很好的选择。

⚠️注意事项
  • 子任务独立性:使用 RecursiveTask 时,需要确保子任务之间无共享状态或相互依赖,且每个子任务都可以独立地完成。

  • 任务分解策略:合理设置任务分解的阈值,避免过度分解导致额外的开销。同时,要考虑任务的平衡性,以减少等待时间。

  • 异常处理:在 RecursiveTask 中处理异常时,需要特别注意。由于任务可能并行执行,异常处理可能会变得复杂。

上面就是 Fork/Join 的核心类,如果要了解它们的详细用法,请继续阅读后续章节……

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号