并行编程
现在计算机大多已向多CPU方向发展,,为了充分利用多CPU、多核CPU的性能优势,计算机软件系统应该可以充分挖掘每个cpu的计算能力,为了充分利用多CPU、多核CPU的优势,可以考虑把一个任务拆分成多个小任务,把多个小任务放到多个处理器核心上并行执行;当多个小任务执行完成后,再将这些执行任务合并起来即可。
JDK7引入了新的Fork/Join框架用于并行编程,从而利用多核处理器。一个问题分为不重叠的子问题,这些子问题可以并行地独立解决。然后合并所有子问题的结果获得问题的整体结果。这是分而治之方法的并行实现。一个分解(fork)可以看作运行在一个线程上的独立任务。
框架使用ForkJoinTask类定义一个任务;同时,在一个ForkJoinPool的实例中执行一个任务。ForkJoinPool是ExecutorService的实现类,因此是一种特殊的线程池。
ForkJoinTask是用于任务的抽象基类。一个ForkJoinTask是一个类似线程的实体,但是比普通的线程轻量级得多,因为巨量得任务和子任务可以被ForkJoinPool中得少数真正得线程所执行。任务主要使用fork()和join()来协调。在一个任务上调用fork()会安排异步得执行,然后调用join()等待任务完成。invoke()和invokeAll(tasks)方法都隐式地调用fork()来执行任务,以及join()等待任务完成,如果有结果则返回结果。
Fork/Join框架是设计用于并行得分而治之解决方案,分而治之本身是递归得。RecursiveAction和RecursiveTask是ForkJoinTask的两个子类。要定义具体的任务类根据情况继承这两个子类。自己的任务重写compute()方法来指定任务是如何执行的。
- RecursiveAction:定义不返回值的任务
- RecursiveTask:定义返回值的任务
package edu.uestc.avatar; import java.util.Arrays; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveAction; public class ParallelMergeSort { /** * 分而治之:递归的 * ForkJoinTask是用于任务的抽象类 * RecursiveAction:用于不返回值的任务 * RecursiveTask:用于返回值的任务 * 任务是基于fork()和join()方法来协调 * fork():异步安排任务执行,然后调用join()等待任务完成 * */ private static class SortTask extends RecursiveAction{ private static final long serialVersionUID = 1L; //终止递归分解的阈值 private final int THRESHOLD = 500; private int[] list; public SortTask(int[] list) { this.list = list; } @Override //告诉任务完成什么 protected void compute() { if(list.length < THRESHOLD)//终止递归条件:线性表较小的时候采用顺序解决方式效率更高 Arrays.sort(list); else { int[] firstHalf = new int[list.length / 2]; System.arraycopy(list, 0, firstHalf, 0, list.length / 2); int secondHalfLength = list.length - list.length / 2; int[] secondHalf = new int[secondHalfLength]; System.arraycopy(list, list.length / 2, secondHalf, 0, secondHalfLength); /* * invoke()/invokeAll():隐式地调用了fork()来执行任务,以及join()方法等待任务完成 * 通过invokeAll方法来调用子任务,该方法在所有子任务都完成后将返回。 * 注意:每个子任务又进一步递归分解为更小的子任务。巨量的子任务可以在池中创建和执行。Fork/Join框架高效地自动执行和协调所有的任务 */ invokeAll(new SortTask(firstHalf),new SortTask(secondHalf)); MergeSort.merge(secondHalf, firstHalf, list); } } } /** * 程序创建一个RecursiveAction,一个ForkJoinPool,然后将该任务放在ForkJoinPool中执行 * @param list */ private static void parallelMergeSort(int[] list) { RecursiveAction task = new SortTask(list); ForkJoinPool pool = new ForkJoinPool(); //invoke方法在主任务执行完后将返回 pool.invoke(task); } public static void main(String[] args) { final int SIZE = 8000000; int[] list1 = new int[SIZE]; int[] list2 = new int[SIZE]; //初始化线性表也可以并行化(应该避免使用Math.random(),因为它是同步执行的,不可以并行执行) for(int i = 0; i < SIZE; i++) list1[i] = list2[i] = (int)(Math.random() * 100000000); long start = System.currentTimeMillis(); MergeSort.mergeSort(list1); System.out.println("普通归并排序:" + (System.currentTimeMillis() - start) + "ms"); start = System.currentTimeMillis(); parallelMergeSort(list2); System.out.println("并行归并排序:" + (System.currentTimeMillis() - start) + "ms"); } }
通过执行结果查看并行排序要比顺序排序快很多。
示例:在线性表中查找最大数(有返回值)
package edu.uestc.avatar; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveTask; public class ParallelMax { public static void main(String[] args) { final int SIZE = 10000000; int[] list = new int[SIZE]; for(int i = 0; i < SIZE; i++) list[i] = i; long start = System.currentTimeMillis(); System.out.println("最大值为:" + max(list)); System.out.println("并行处理器数量:" + Runtime.getRuntime().availableProcessors()); System.out.println("花费时间:" + (System.currentTimeMillis() - start) + "ms"); } private static int max(int[] list) { MaxTask task = new MaxTask(list,0,list.length); ForkJoinPool pool = new ForkJoinPool(); return pool.invoke(task);//执行主任务 } private static class MaxTask extends RecursiveTask<Integer>{ /** * */ private static final long serialVersionUID = -3613298506153982577L; //终止递归分解的阈值 private final int THRESHOLD = 1000; private int[] list; private int low; private int high; public MaxTask(int[] list, int low, int high) { this.list = list; this.low = low; this.high = high; } @Override protected Integer compute() { if(high - low < THRESHOLD) { int max = list[0]; for(int i = low; i < high; i++) { if(list[i] > max) max = list[i]; } return max;//自动装箱 }else { int mid = (low + high) / 2; //分解成两个子任务,分别找到左边和右半边的最大元素 RecursiveTask<Integer> left = new MaxTask(list, low, mid); RecursiveTask<Integer> right = new MaxTask(list, mid, high); //每个子任务又递归的分解成更多的子任务 left.fork(); right.fork(); return Math.max(left.join(),right.join()); } } } }