分治思想
将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。
步骤:
- 分解:将要解决的问题划分成若干小的同类问题
- 求解:当子问题划分得足够小时,用较简单的方法解决
- 合并:按原问题的要求,将子问题的解逐层合并构成原问题的解
归并排序
归并排序(Merge Sort)是一种基于分治思想的排序算法。归并排序将一个大数组分成两个相等大小的子数组,对每个子数组分别进行排序,然后将两个子数组合并成一个有序的大数组。
单线程示例:
1. 对2千万数组排序
public class MergeSort {
public static int[] mergeSort(int[] arrays, int len) {
int mid = arrays.length / 2;
// 终止条件
if (mid <= len) {
Arrays.sort(arrays);
return arrays;
}
// 拆分任务
int[] left = Arrays.copyOfRange(arrays, 0, mid);
int[] right = Arrays.copyOfRange(arrays, mid, arrays.length);
// 分别计算子任务
left = mergeSort(left, len);
right = mergeSort(right, len);
// 合并子任务结果
return merge(left, right);
}
// 合并两个有序的数组
private static int[] merge(int[] left, int[] right) {
int[] result = new int[left.length + right.length];
int leftIndex = 0, rightIndex = 0, resultIndex = 0;
while (leftIndex < left.length && rightIndex < right.length) {
if (left[leftIndex] <= right[rightIndex]) {
result[resultIndex++] = left[leftIndex++];
} else {
result[resultIndex++] = right[rightIndex++];
}
}
while (leftIndex < left.length) {
result[resultIndex++] = left[leftIndex++];
}
while (rightIndex < right.length) {
result[resultIndex++] = right[rightIndex++];
}
return result;
}
}
耗时结果
ForkJoinPool
原理
Fork/Join框架是JAVA7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干小任务,最终汇总每个小任务结果得到大任务结果的框架。
ForkJoinPool基于Fork/Join框架实现的线程池, 主要采用分治算法和工作窃取算法
- 任务切分:将大的任务分割成更小粒度的小任务,让更多的线程参与执行
- 任务窃取:通过任务窃取,充分地利用空闲线程,并减少竞争
构造函数
public ForkJoinPool(int parallelism,
ForkJoinWorkerThreadFactory factory,
UncaughtExceptionHandler handler,
boolean asyncMode) {
this(checkParallelism(parallelism),
checkFactory(factory),
handler,
asyncMode ? FIFO_QUEUE : LIFO_QUEUE,
"ForkJoinPool-" + nextPoolId() + "-worker-");
checkPermission();
}
- parallelism: 并行线程数
- factory: 创建线程工厂
- handler: 异常处理器
- asycnMode: 队列工作模式. true:先进先出; false:后进先出
使用示例
继承ForkJoinTask类, 实现compute方法
递归操作提供了两个封装抽象类RecursiveAction和RecursiveTask
如果不需要返回值用RecursiveAction, 如果不需要返回值用RecursiveTask
示例1: ForkJoin多线程归并排序
@Getter
public class MergeSortTask extends RecursiveAction {
private final int len;
private int[] arrays;
public MergeSortTask(int len, int[] arrays) {
this.len = len;
this.arrays = arrays;
}
@Override
protected void compute() {
int mid = arrays.length / 2;
// 终止条件
if (mid <= len) {
Arrays.sort(arrays);
return;
}
// 拆分任务
int[] left = Arrays.copyOfRange(arrays, 0, mid);
int[] right = Arrays.copyOfRange(arrays, mid, arrays.length);
// 创建子任务
MergeSortTask leftTak = new MergeSortTask(len, left);
MergeSortTask rightTak = new MergeSortTask(len, right);
// 多线程处理子任务
invokeAll(leftTak, rightTak);
// 合并子任务结果
arrays = MergeSort.merge(leftTak.getArrays(), rightTak.getArrays());
}
}
运行结果:
示例. ForkJoin多线程归并求和
@Getter
public class MergeSumTask extends RecursiveTask<Long> {
private final int len;
private int[] arrays;
public MergeSumTask(int len, int[] arrays) {
this.len = len;
this.arrays = arrays;
}
@Override
protected Long compute() {
int mid = arrays.length / 2;
// 终止条件
if (mid <= len) {
return MergeSum.sum(arrays);
}
// 拆分任务
int[] left = Arrays.copyOfRange(arrays, 0, mid);
int[] right = Arrays.copyOfRange(arrays, mid, arrays.length);
// 创建子任务
MergeSumTask leftTak = new MergeSumTask(len, left);
MergeSumTask rightTak = new MergeSumTask(len, right);
// 多线程处理子任务
leftTak.fork();
rightTak.fork();
// 合并子任务结果
return leftTak.join() + rightTak.join();
}
}
执行结果: 求和操作建议直接用单线程
注意事项
- 避免提交大量阻塞任务, 建议阻塞任务和非阻塞任务分别使用独立的线程池
- 避免调用链路太深, 可能导致栈溢出, 例如用递归计算斐波那契数列会抛异常
- 如果是纯函数运算, 可考虑用ForkJoin