当前位置: 首页 > 编程笔记 >

Java Fork/Join框架

陶朝明
2023-03-14
本文向大家介绍Java Fork/Join框架,包括了Java Fork/Join框架的使用技巧和注意事项,需要的朋友参考一下

Fork/Join框架是ExecutorService接口的一个实现,通过它我们可以实现多进程。Fork/Join可以用来将一个大任务递归的拆分为多个小任务,目标是充分利用所有的资源尽可能增强应用的性能。

和任何ExecutorService接口的实现一样,Fork/Join也会使用线程池来分布式的管理工作线程。Fork/Join框架的独特之处在于它使用了work-stealing(工作窃取)算法。通过这个算法,工作线程在无事可做时可以窃取其它正在繁忙的线程的任务来执行。

Fork/Join框架的核心是ForkJoinPool类,一个AbstractExecutorService类的子类。ForkJoinPool实现了核心的work-stealing算法并可以执行ForkJoinTask处理。

基础用法

使用Fork/Join框架的第一步是编写执行碎片任务的代码。要编写的代码类似如下伪代码:

if 任务足够小:
 直接执行任务
else:
 将任务切成两个小任务
 执行两个小任务并等待结果

使用ForkJoinTask子类来封装如上的代码,通常会使用一些JDK提供的类,使用的有RecursiveTask(这个类会返回一个结果)和RecursiveAction两个类。

在准备好ForkJoinTask子类后,创建一个代表所有任务的对象,并将之传递给一个ForkJoinPool实例的invoke()方法。

由模糊到清晰

为了辅助理解Fork/Join框架是如何工作的,我们使用一个案例来进行说明:比如对一张图片进行模糊处理。我们用一个整型数组表示图片,其中的每个数值代表一个像素的颜色。被模糊的图片也用一个同等长度的数组来表示。

执行模糊是通过对代表图片的每个像素进行处理实现的。计算每个像素与其周围像素的均值(红黄蓝三原色的均值),计算生成的结果数组就是模糊后的图片。由于代表图像的通常都是一个大数组,整个处理过程需要通常会需要很多时间。可以使用Fork/Join框架利用多处理器系统上的并发处理优势来进行提速。下面是一个可能的实现:

package com.zhyea.robin;
 
import java.util.concurrent.RecursiveAction;
 
public class ForkBlur extends RecursiveAction {
 
  private int[] mSource;
  private int mStart;
  private int mLength;
  private int[] mDestination;
 
  // 处理窗口大小; 需要是一个奇数.
  private int mBlurWidth = 15;
 
  public ForkBlur(int[] src, int start, int length, int[] dst) {
    mSource = src;
    mStart = start;
    mLength = length;
    mDestination = dst;
  }
 
  protected void computeDirectly() {
    int sidePixels = (mBlurWidth - 1) / 2;
    for (int index = mStart; index < mStart + mLength; index++) {
      // 计算平均值.
      float rt = 0, gt = 0, bt = 0;
      for (int mi = -sidePixels; mi <= sidePixels; mi++) {
 
        int mindex = Math.min(Math.max(mi + index, 0), mSource.length - 1);
 
        int pixel = mSource[mindex];
        rt += (float) ((pixel & 0x00ff0000) >> 16) / mBlurWidth;
        gt += (float) ((pixel & 0x0000ff00) >> 8) / mBlurWidth;
        bt += (float) ((pixel & 0x000000ff) >> 0) / mBlurWidth;
      }
 
      // 重组目标像素.
      int dpixel = (0xff000000) |
          (((int) rt) << 16) |
          (((int) gt) << 8) |
          (((int) bt) << 0);
      mDestination[index] = dpixel;
    }
  }
  
  ....
}

现在实现抽象方法compute(),在这个方法中既实现了模糊操作,也实现了将一个任务拆分成两个小任务。这里仅是简单依据数组长度来决定是直接执行任务还是将之拆分成两个小任务:

  protected static int sThreshold = 100000;
 
  protected void compute() {
    if (mLength < sThreshold) {
      computeDirectly();
      return;
    }
 
    int split = mLength / 2;
 
    invokeAll(new ForkBlur(mSource, mStart, split, mDestination),
        new ForkBlur(mSource, mStart + split, mLength - split,
            mDestination));
  }

因为上面这些方法的实现是定义在RecursiveAction的一个子类中,可以直接在一个ForkJoinPool中创建并运行任务。具体步骤如下:

1. 创建一个代表要执行的任务的对象:

// src 表示源图片像素的数组
// dst 表示生成的图片的像素
ForkBlur fb = new ForkBlur(src, 0, src.length, dst);

2. 创建一个运行任务的ForkJoinPool实例:

ForkJoinPool pool = new ForkJoinPool();

3. 运行任务:

pool.invoke(fb);

在源代码中还包含了一些创建目标图片的代码。具体参考ForkBlur示例。

标准实现

要使用Fork/Join框架按自定义的算法在多核系统上执行并发任务当然需要实现自定义的类了(比如之前我们实现的ForkBlur类)。除此之外,在JavaSE中已经在广泛使用Fork/Join框架的一些特性了。比如Java8中的java.util.Arrays类的parallelSort()方法就使用了Fork/Join框架。具体可以参考Java API文档。

Fork/Join框架的另一个实现在java.util.streams包下,这也是java8的Lambda特性的一部分。

 类似资料:
  • fork-join框架允许在几个worker上中断某个任务,然后等待结果将它们组合起来。 它在很大程度上利用了多处理器机器的容量。 以下是fork-join框架中使用的核心概念和对象。 Fork Fork是一个过程,在这个过程中,任务将自身分成较小且独立的子任务,这些子任务可以同时执行。 语法 (Syntax) Sum left = new Sum(array, low, mid); left.

  • 主要内容:Fork,Join,ForkJoinPool,RecursiveAction,递归任务,实例框架允许在几个工作进程中断某个任务,然后等待结果组合它们。 它在很大程度上利用了多处理器机器的生产能力。 以下是框架中使用的核心概念和对象。 Fork Fork是一个进程,其中任务将其分成可以并发执行的较小且独立的子任务。 语法 这里是的子类,方法将任务分解为子任务。 Join 连接()是子任务完成执行后任务加入子任务的所有结果的过程,否则它会持续等待。 语法 这里剩下的是类的一个对象。 For

  • 在Doug Lea的论文“A Java Fork/Join Framework”中: http://gee.cs.oswego.edu/dl/papers/fj.pdf 在2.1偷工中,他说: 当工作线程遇到连接操作时,它会处理其他任务(如果可用),直到注意到目标任务已完成(通过isDone)。否则,所有任务都将在不阻塞的情况下运行到完成。 那么有人能具体告诉我这些“其他任务”来自哪里吗?它们来自

  • 在上面,我认为fork-join执行器的代码并行是不可能的。对不同方法/函数的每次调用都需要上一步的内容。如果我要为线程选择fork-join执行器,这对我有什么好处呢?在fork-join和线程池执行器之间,上面的代码执行会有什么不同。 谢谢

  • 问题内容: 使用新的fork / join框架 有什么好处,而不是仅在开始时将大任务简单地拆分为N个子任务,然后将它们发送到缓存的线程池(来自Executors),然后等待每个任务完成?我看不到使用fork / join抽象如何简化问题或使解决方案比我们多年以来的效率更高。 例如,本教程示例中的并行化模糊算法可以这样实现: 首先拆分,然后将任务发送到线程池: 任务进入线程池的队列,当工作线程可用时

  • 本文向大家介绍php的lavarel框架中join和orWhere的用法,包括了php的lavarel框架中join和orWhere的用法的使用技巧和注意事项,需要的朋友参考一下 Laravel是一个开源PHP框架,功能强大且易于理解。它遵循模型 - 视图 - 控制器设计模式(MVC)。Laravel重用了不同框架的现有组件,这有助于创建Web应用程序。这样设计的Web应用程序更加结构化和实用。