线程池FockJoinPoolg工作原理

87 阅读4分钟

如何快速高效的对千万大小数据进行排序

归并排序(Merge Sort)是一种基于分治思想的排序算法。归并排序的基本思想是将一个大数组分成 两个相等大小的子数组,对每个子数组分别进行排序,然后将两个子数组合并成一个有序的大数组。 因为常常使用递归实现(由先拆分后合并的性质决定的),所以我们称其为归并排序。

归并排序的步骤包括以下几个方面:

  • 将数组分成两个子数组
  • 对每个子数组进行排序
  • 合并两个有序的子数组

归并排序的时间复杂度为O(nlogn),空间复杂度为O(n),其中n为数组的长度。 分治思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。

分治思想的步骤如下:

分解:将要解决的问题划分成若干规模较小的同类问题;

求解:当子问题划分得足够小时,用较简单的方法解决;

合并:按原问题的要求,将子问题的解逐层合并构成原问题的解

单线程和多线程并行场景下归并排序性能对比

单线程实现归并排序

单线程归并算法的实现,它的基本思路是将序列分成两个部分,分别进行递归排序,然后将排序好的 子序列合并起来。

` public class MergeSort {

private final int[] arrayToSort; //要排序的数组
private final int threshold;  //拆分的阈值,低于此阈值就不再进行拆分

public MergeSort(final int[] arrayToSort, final int threshold) {
    this.arrayToSort = arrayToSort;
    this.threshold = threshold;
}

/**
 * 排序
 * @return
 */
public int[] mergeSort() {
    return mergeSort(arrayToSort, threshold);
}

public static int[] mergeSort(final int[] arrayToSort, int threshold) {
    //拆分后的数组长度小于阈值,直接进行排序
    if (arrayToSort.length < threshold) {
        //调用jdk提供的排序方法
        Arrays.sort(arrayToSort);
        return arrayToSort;
    }

    int midpoint = arrayToSort.length / 2;
    //对数组进行拆分
    int[] leftArray = Arrays.copyOfRange(arrayToSort, 0, midpoint);
    int[] rightArray = Arrays.copyOfRange(arrayToSort, midpoint, arrayToSort.length);
    //递归调用
    leftArray = mergeSort(leftArray, threshold);
    rightArray = mergeSort(rightArray, threshold);
    //合并排序结果
    return merge(leftArray, rightArray);
}

public static int[] merge(final int[] leftArray, final int[] rightArray) {
    //定义用于合并结果的数组
    int[] mergedArray = new int[leftArray.length + rightArray.length];
    int mergedArrayPos = 0;
    // 利用双指针进行两个数的比较
    int leftArrayPos = 0;
    int rightArrayPos = 0;
    while (leftArrayPos < leftArray.length && rightArrayPos < rightArray.length) {
        if (leftArray[leftArrayPos] <= rightArray[rightArrayPos]) {
            mergedArray[mergedArrayPos] = leftArray[leftArrayPos];
            leftArrayPos++;
        } else {
            mergedArray[mergedArrayPos] = rightArray[rightArrayPos];
            rightArrayPos++;
        }
        mergedArrayPos++;
    }

    while (leftArrayPos < leftArray.length) {
        mergedArray[mergedArrayPos] = leftArray[leftArrayPos];
        leftArrayPos++;
        mergedArrayPos++;
    }

    while (rightArrayPos < rightArray.length) {
        mergedArray[mergedArrayPos] = rightArray[rightArrayPos];
        rightArrayPos++;
        mergedArrayPos++;
    }

    return mergedArray;
}

}

`

Java并行框架Fork/Join使用详解

Fork/Join并行归并排序

并行归并排序是一种利用多线程实现的归并排序算法。它的基本思路是将数据分成若干部分,然后在 不同线程上对这些部分进行归并排序,最后将排好序的部分合并成有序数组。在多核CPU上,这种算 法也能够有效提高排序速度。

可以使用Java的Fork/Join框架来实现归并排序的并行化

` public class MergeSortTask extends RecursiveAction {

private final int threshold; //拆分的阈值,低于此阈值就不再进行拆分
private int[] arrayToSort; //要排序的数组

public MergeSortTask(final int[] arrayToSort, final int threshold) {
	this.arrayToSort = arrayToSort;
	this.threshold = threshold;
}

@Override
protected void compute() {
	//拆分后的数组长度小于阈值,直接进行排序
	if (arrayToSort.length <= threshold) {
		// 调用jdk提供的排序方法
		Arrays.sort(arrayToSort);
		return;
	}

	// 对数组进行拆分
	int midpoint = arrayToSort.length / 2;
	int[] leftArray = Arrays.copyOfRange(arrayToSort, 0, midpoint);
	int[] rightArray = Arrays.copyOfRange(arrayToSort, midpoint, arrayToSort.length);

	MergeSortTask leftTask = new MergeSortTask(leftArray, threshold);
	MergeSortTask rightTask = new MergeSortTask(rightArray, threshold);

	//调用任务,阻塞当前线程,直到所有子任务执行完成
	invokeAll(leftTask,rightTask);
	//提交任务

// leftTask.fork(); // rightTask.fork(); // //合并结果 // leftTask.join(); // rightTask.join();

	// 合并排序结果
	arrayToSort = MergeSort.merge(leftTask.getSortedArray(), rightTask.getSortedArray());
}

public int[] getSortedArray() {
	return arrayToSort;
}

} `

在这个示例中,我们使用Fork/Join框架实现了归并排序算法,并通过递归调用实现了并行化。使用 Fork/Join框架实现归并排序算法的关键在于将排序任务分解成小的任务,使用Fork/Join框架将这些小 任务提交给线程池中的不同线程并行执行,并在最后将排序后的结果进行合并。这样可以充分利用多 核CPU的并行处理能力,提高程序的执行效率。

测试结果对比

测试代码

` import java.util.Random;

public class Utils {

/**
 * 随机生成数组
 * @param size 数组的大小
 * @return
 */
public static int[] buildRandomIntArray(final int size) {
	int[] arrayToCalculateSumOf = new int[size];
	Random generator = new Random();
	for (int i = 0; i < arrayToCalculateSumOf.length; i++) {
		arrayToCalculateSumOf[i] = generator.nextInt(100000000);
	}
	return arrayToCalculateSumOf;
}

} `

` import com.tuling.learnjuc.forkjoin.util.Utils;

import java.util.Arrays; import java.util.concurrent.ForkJoinPool;

public class ArrayToSortMain {

public static void main(String[] args) {
    //生成测试数组  用于归并排序
    int[] arrayToSortByMergeSort = Utils.buildRandomIntArray(20000000);
    //生成测试数组  用于forkjoin排序
    int[] arrayToSortByForkJoin = Arrays.copyOf(arrayToSortByMergeSort, arrayToSortByMergeSort.length);
    //获取处理器数量
    int processors = Runtime.getRuntime().availableProcessors();


    MergeSort mergeSort = new MergeSort(arrayToSortByMergeSort, processors);
    long startTime = System.nanoTime();
    // 归并排序
    mergeSort.mergeSort();
    long duration = System.nanoTime()-startTime;
    System.out.println("单线程归并排序时间: "+(duration/(1000f*1000f))+"毫秒");

    //利用forkjoin排序
    MergeSortTask mergeSortTask = new MergeSortTask(arrayToSortByForkJoin, processors);
    //构建forkjoin线程池
    ForkJoinPool forkJoinPool = new ForkJoinPool(processors);
    startTime = System.nanoTime();
    //执行排序任务
    forkJoinPool.invoke(mergeSortTask);
    duration = System.nanoTime()-startTime;
    System.out.println("forkjoin排序时间: "+(duration/(1000f*1000f))+"毫秒");

}

} `

根据测试结果可以看出,数组越大,利用Fork/Join框架实现的并行化归并排序比单线程归并排序的效 率更高

Fork/Join处理递归任务和阻塞任务注意事项

ForkJoinPool工作原理分析