简单实例详解多线编程的Fork/Join应用


简单实例详解多线编程的Fork/Join应用

文章插图
 
前言前面我们通过一篇文章讲过多线程应用开发过程中,我们遇到的如何对线程进行设计和应用的问题,其中我们提到了一个分离和联合线程模型 。
在JAVA的1.7版以后,它提供了一个Fork/Join框架 。用来实现一种常见的多线程处理模型设计 。
Fork/JoinFork/Join框架的基类是
java.util.concurrent.ForkJoinPool 。
这个类实现了Executor和ExecutorService两个接口以及AbstractExecutorService抽象类 。
所以ForkJoin是一个特殊的线程池实现 。
因此,ForkJoin线程池基本上是一个执行特殊任务的线程池,即ForkJoinTask 。
这个类实现了已知的接口Future,并使用了get()、cancel()和isDone()等方法 。
除此之外,这个类还提供了两个方法,它们为整个框架提供了名称:fork()和join() 。
调用fork()将启动任务的异步执行,而调用join()将等待任务完成并检索其结果 。
因此,我们可以将一个给定的任务分解成多个较小的任务,派生每个任务,最后等待所有任务完成 。这使得复杂问题的实现更加容易 。
在计算机科学中,这种方法也称为分治法 。
每当一个问题太复杂而不能一次解决时,它就被分解成多个更小、更容易解决的问题 。这可以用伪代码写成:
简单实例详解多线编程的Fork/Join应用

文章插图
 
首先,我们检查问题的当前大小是否大于给定的阈值 。
如果是这种情况,我们将问题分成更小的问题,fork()每个新任务,然后通过调用join()等待结果 。
当join()返回每个子任务的结果时,我们必须找到较小问题的最佳解决方案,并将其作为最佳解决方案返回 。
重复这些步骤,直到给定的阈值太低,并且问题太小,我们可以直接计算其解决方案,而无需进一步的除法 。
简单实例详解多线编程的Fork/Join应用

文章插图
 
RecursiveTask为了更好地理解这个过程,我们实现了一个算法,它可以找到整数值数组中最小的数 。
这个问题不是我们在日常工作中使用ForkJoinPool可以解决的问题,但是下面的实现非常清楚地展示了基本原则 。
在main()方法中,我们设置一个带有随机值的整数数组,并创建一个新的ForkJoinPool 。
传递给其构造函数的第一个参数是指示所需并行度的级别 。在这里,我们查询运行时中可用的CPU内核的数量 。
然后调用invoke()方法并传递一个GetMinNumb实例 。
GetMinNumb扩展了类RecursiveTask,它本身是前面提到的ForkJoinTask的子类 。
ForkJoinTask类实际上有两个子类:一个是为返回值的任务设计的(RecursiveTask),另一个是为没有返回值的任务设计的(RecursiveAction) 。
超类要求我们必须实现compute()方法 。在这里,我们查看整数数组的给定部分,并决定当前的问题是否太大而不能立即解决,需要分解处理 。
在寻找数组中最小的数字时,需要直接解决的最小问题大小是比较两个元素并返回它们的最小值 。
如果当前有两个以上的元素,则将数组分成两部分,并再次找到这两部分中最小的数 。这是通过创建GetMinNumb的两个新实例来实现的 。
构造函数由数组和开始和结束索引组成 。然后,通过调用fork()来异步地启动这两个任务的执行 。这个调用提交线程池队列中的两个任务 。
线程池实现了一种称为“工作窃取”的策略,即如果所有其他线程都有足够的工作要做,则当前线程从其他任务之一窃取其工作 。这确保任务尽可能快地执行 。
简单实例详解多线编程的Fork/Join应用

文章插图
 
RecursiveAction如上所述,在RecursiveTask旁边还有RecursiveAction类 。
与RecursiveTask不同的是,它不必返回值,因此它可以用于可以直接在给定数据结构上执行的异步计算 。
这样一个例子是计算灰度图像的彩色图像 。我们所要做的就是对图像的每个像素进行迭代,并使用以下公式从RGB值中计算出灰度值:
gray = 0.2126 * red + 0.7152 * green + 0.0722 * blue浮点数表示特定颜色对人类对灰色感知的影响程度 。由于绿色使用的是最大值,我们可以得出一个灰度图像的计算结果接近绿色部分的3/4 。
因此,基本实现应该是这样的,假设image是我们表示实际像素数据的对象,setRGB()和getRGB()方法用于检索实际的RGB值:
简单实例详解多线编程的Fork/Join应用

文章插图


推荐阅读