阅读 99

关于Java中的Fork/Join

这是我参与 8 月更文挑战的第 19 天,活动详情查看: 8月更文挑战

Fork/Join框架

Fork/Join框架应用“分而治之”的思想,即:通过fork()拆分子任务,通过join()获取子任务的计算结果。 image.png 要使用Fork/Join框架完成分治,我们要考虑3个问题:

  1. 什么时候是最小可执行任务
  2. 什么时候拆分任务
  3. 什么时候汇总任务的结果

使用Fork/Join框架完成的归并排序

public class MergeSortTask extends RecursiveTask<int[]> {

  private int array[];

  private int n;

  public MergeSortTask(int[] array, int n) {
    this.array = array;
    this.n = n;
  }

  @Override
  protected int[] compute() {
    if (array.length <= n) {
      Arrays.sort(array);
      return array;
    } else {
      MergeSortTask task1 = new MergeSortTask(Arrays.copyOf(array, array.length / 2), n);
      task1.fork();
      MergeSortTask task2 = new MergeSortTask(Arrays.copyOfRange(array, array.length / 2, array.length), n);
      task2.fork();
      return merge(task1.join(), task2.join());
    }
  }


  private static int[] merge(int array1[], int array2[]) {
    int length1 = array1.length;
    int length2 = array2.length;
    int[] result = new int[length1 + length2];
    for (int index = 0, index1 = 0, index2 = 0; index < result.length; index++) {
      int value1 = index1 >= length1 ? Integer.MAX_VALUE : array1[index1];
      int value2 = index2 >= length2 ? Integer.MAX_VALUE : array2[index2];
      if (value1 < value2) {
        index1++;
        result[index] = value1;
      } else {
        index2++;
        result[index] = value2;
      }
    }
    return result;
  }

}
复制代码

ForkJoinPool解析

ForkJoinPool是基于Fork/Join框架的池化,如下图所示,任务提交后路由到不同的双端队列中(一个WorkQueue数组),每个队列分别对应一个线程

image.png

任务的来源有两种,分别是外部提交和内部fork,前者放到WorkQueue数组的偶数下标中,后者放在奇数下标中,且只有奇数下标才有线程与之对应,而其他队列中的任务通过工作窃取完成。

既然WorkQueue是一个双端队列,那么必然存在两种消费方式:FIFO和LIFO,默认是LIFO,即将其看做一个栈,消费时从top端取任务,工作窃取时从base端取任务,这样可以保证大任务先被分解成小任务。

image.png

另外,join操作不会阻塞线程,会将阻塞的任务放回队列中,继续处理下一个任务。Fork-Join框架通过在执行线程和任务之间引入一个“中间层”,允许线程将阻塞的任务放在一边并且等所有它所依赖的子任务全部处理完成后,再去处理这个因为子任务而阻塞的任务。也就是说,假设“任务1”依赖“任务5”(其中,任务5是由任务1创建的子任务),那么任务1就被放到一边,去执行“任务5”。

图1中的2路3层的Fork-Join产生了2^3个并发线程,即K路N层的Fork-Join任务会产生K^N个并发线程,那么如何确定递归深度是一个需要考虑的问题。

我们测试对一个长度为100万的乱序数组进行排序,最小排序数量从1到100万,其结果显示,当n=1时,由于进行完全切分,任务数量多耗时较长,而n=2时任务数量马上减为一半且运算简单,使得速度显著提升。当n>=长度时,完全不切分任务,相当于一个线程进行快排,速度显著变慢。

测试结果具体数据

最小排序数量=1,耗时=76.2

最小排序数量=11,耗时=39.4

最小排序数量=21,耗时=36.1

最小排序数量=31,耗时=36.2

最小排序数量=41,耗时=37.9

最小排序数量=51,耗时=36.0

最小排序数量=61,耗时=66.8

最小排序数量=71,耗时=44.5

最小排序数量=81,耗时=36.6

最小排序数量=91,耗时=37.5

最小排序数量=10000,耗时=36.4

最小排序数量=20000,耗时=39.2

最小排序数量=30000,耗时=29.8

最小排序数量=40000,耗时=29.7

最小排序数量=50000,耗时=30.9

最小排序数量=60000,耗时=28.5

最小排序数量=70000,耗时=29.6

最小排序数量=80000,耗时=31.2

最小排序数量=90000,耗时=29.5

最小排序数量=100000,耗时=40.0

最小排序数量=110000,耗时=34.6

最小排序数量=120000,耗时=29.6

最小排序数量=130000,耗时=34.1

最小排序数量=140000,耗时=52.7

最小排序数量=150000,耗时=33.9

最小排序数量=160000,耗时=33.3

最小排序数量=170000,耗时=29.9

最小排序数量=180000,耗时=34.1

最小排序数量=190000,耗时=32.4

最小排序数量=200000,耗时=33.3

最小排序数量=210000,耗时=32.5

最小排序数量=220000,耗时=32.2

最小排序数量=230000,耗时=33.9

最小排序数量=240000,耗时=32.3

最小排序数量=250000,耗时=32.8

最小排序数量=260000,耗时=33.1

最小排序数量=270000,耗时=34.3

最小排序数量=280000,耗时=33.9

最小排序数量=290000,耗时=32.2

最小排序数量=300000,耗时=41.5

最小排序数量=310000,耗时=33.0

最小排序数量=320000,耗时=34.7

最小排序数量=330000,耗时=34.5

最小排序数量=340000,耗时=33.2

最小排序数量=350000,耗时=33.6

最小排序数量=360000,耗时=32.2

最小排序数量=370000,耗时=35.1

最小排序数量=380000,耗时=34.2

最小排序数量=390000,耗时=32.7

最小排序数量=400000,耗时=33.0

最小排序数量=410000,耗时=33.4

最小排序数量=420000,耗时=34.3

最小排序数量=430000,耗时=34.2

最小排序数量=440000,耗时=32.3

最小排序数量=450000,耗时=34.9

最小排序数量=460000,耗时=34.3

最小排序数量=470000,耗时=32.9

最小排序数量=480000,耗时=34.2

最小排序数量=490000,耗时=34.4

最小排序数量=500000,耗时=47.0

最小排序数量=510000,耗时=45.9

最小排序数量=520000,耗时=50.1

最小排序数量=530000,耗时=49.2

最小排序数量=540000,耗时=50.7

最小排序数量=550000,耗时=46.4

最小排序数量=560000,耗时=49.6

最小排序数量=570000,耗时=48.0

最小排序数量=580000,耗时=46.2

最小排序数量=590000,耗时=44.8

最小排序数量=600000,耗时=47.9

最小排序数量=610000,耗时=49.2

最小排序数量=620000,耗时=58.5

最小排序数量=630000,耗时=65.2

最小排序数量=640000,耗时=55.8

最小排序数量=650000,耗时=45.9

最小排序数量=660000,耗时=50.8

最小排序数量=670000,耗时=44.3

最小排序数量=680000,耗时=45.6

最小排序数量=690000,耗时=48.5

最小排序数量=700000,耗时=45.6

最小排序数量=710000,耗时=44.7

最小排序数量=720000,耗时=45.0

最小排序数量=730000,耗时=46.7

最小排序数量=740000,耗时=45.9

最小排序数量=750000,耗时=45.4

最小排序数量=760000,耗时=45.0

最小排序数量=770000,耗时=45.2

最小排序数量=780000,耗时=45.6

最小排序数量=790000,耗时=50.8

最小排序数量=800000,耗时=45.9

最小排序数量=810000,耗时=45.3

最小排序数量=820000,耗时=47.9

最小排序数量=830000,耗时=44.0

最小排序数量=840000,耗时=45.6

最小排序数量=850000,耗时=47.9

最小排序数量=860000,耗时=45.5

最小排序数量=870000,耗时=46.1

最小排序数量=880000,耗时=45.2

最小排序数量=890000,耗时=48.5

最小排序数量=900000,耗时=45.7

最小排序数量=910000,耗时=45.4

最小排序数量=920000,耗时=49.3

最小排序数量=930000,耗时=46.1

最小排序数量=940000,耗时=46.7

最小排序数量=950000,耗时=44.4

最小排序数量=960000,耗时=45.4

最小排序数量=970000,耗时=45.0

最小排序数量=980000,耗时=47.2

最小排序数量=990000,耗时=48.4

最小排序数量=1000000,耗时=84.3

最小排序数量=1010000,耗时=84.6

文章分类
后端
文章标签