现代的计算机已经向多CPU方向发展,即使是普通的PC,甚至现在的智能手机、多核处理器已被广泛应用。在未来,处理器的核心数将会发展的越来越多。
虽然硬件上的多核CPU已经十分成熟,但是很多应用程序并未这种多核CPU做好准备,因此并不能很好地利用多核CPU的性能优势。
为了充分利用多CPU、多核CPU的性能优势,级软基软件系统应该可以充分“挖掘”每个CPU的计算能力,决不能让某个CPU处于“空闲”状态。为此,可以考虑把一个任务拆分成多个“小任务”,把多个"小任务"放到多个处理器核心上并行执行。当多个“小任务”执行完成之后,再将这些执行结果合并起来即可。
Java在JDK7之后加入了并行计算的框架Fork/Join,可以解决我们系统中大数据计算的性能问题。Fork/Join采用的是分治法,Fork是将一个大任务拆分成若干个子任务,子任务分别去计算,而Join是获取到子任务的计算结果,然后合并,这个是递归的过程。子任务被分配到不同的核上执行时,效率最高。伪代码如下:
- Result solve(Problem problem) {
- if (problem is small)
- directly solve problem
- else {
- split problem into independent parts
- fork new subtasks to solve each part
- join all subtasks
- compose result from subresults
- }
- }
Fork/Join框架的核心类是ForkJoinPool,它能够接收一个ForkJoinTask,并得到计算结果。ForkJoinTask有两个子类,RecursiveTask(有返回值)和RecursiveAction(无返回结果),我们自己定义任务时,只需选择这两个类继承即可
示例代码
- package forkJoin;
-
- import java.util.concurrent.RecursiveTask;
-
- public class SumTask extends RecursiveTask<Integer> {
- private static final int THRESHOLD = 20;
-
- private int[] array;
- private int low;
- private int high;
-
- public SumTask(int[] array, int low, int high) {
- this.array = array;
- this.low = low;
- this.high = high;
- }
-
- @Override
- protected Integer compute() {
- int sum = 0;
- if (high - low + 1 <= THRESHOLD) {
- System.out.println(low + " - " + high + " 计算");
-
-
-
-
-
-
-
-
- for (int i = low; i <= high; i++) {
- sum += array[i];
- }
- } else {
- System.out.println(low + " - " + high + " 切分");
-
- int mid = (low + high) / 2;
- SumTask left = new SumTask(array, low, mid);
- SumTask right = new SumTask(array, mid + 1, high);
-
-
- invokeAll(left, right);
-
-
- sum = left.join() + right.join();
-
-
- try {
- sum = left.get() + right.get();
- } catch (Throwable e) {
- System.out.println(e.getMessage());
- }
- }
- return sum;
- }
- }
- package forkJoin;
-
- import java.util.Random;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.ForkJoinPool;
- import java.util.concurrent.RecursiveAction;
-
- public class Main {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- private static int[] genArray() {
- int[] array = new int[100];
- for (int i = 0; i < array.length; i++) {
- array[i] = new Random().nextInt(500);
- }
- return array;
- }
-
- public static void main(String[] args) throws ExecutionException, InterruptedException {
-
-
-
-
-
- int[] array = genArray();
-
-
- int total = 0;
- for (int i = 0; i < array.length; i++) {
- total += array[i];
- }
- System.out.println("目标和:" + total);
-
-
- SumTask sumTask = new SumTask(array, 0, array.length - 1);
-
-
-
- int processors = Runtime.getRuntime().availableProcessors();
- ForkJoinPool forkJoinPool = new ForkJoinPool(processors * 2);
-
-
- forkJoinPool.submit(sumTask);
-
-
- long begin = System.currentTimeMillis();
-
- Integer result = sumTask.get();
- long end = System.currentTimeMillis();
- System.out.println(String.format("结果 %s ,耗时 %sms", result, end - begin));
-
- if (result == total) {
- System.out.println("测试成功");
- } else {
- System.out.println("fork join 使用失败!!!!");
- }
- }
- }
上面的代码是一个100个整数累加的任务,切分到小于20个数的时候直接进行累加,不再切分。
我们通过调整阈值(THRESHOLD),可以发现耗时是不一样的。实际应用中,如果需要分割的任务大小是固定的,可以经过测试,得到最佳阈值;如果大小不是固定的,就需要设计一个可伸缩的算法,来动态计算出阈值。如果子任务很多,效率并不一定会高。
PS:类似的这种“分而治之”的需求场景,往往带有递归性,实际中,我们可以考虑任务是否具有“递归性”来决定是否使用“Fork-Join”框架。
转自:http://blog.csdn.net/conquer0715/article/details/51282390