Fork/Join由来-分而治之思想
分而治之:对于一个比较复杂的任务,如果可以很自然地将其分解为多个子任务,这些子任务互相独立且与原问题性质相同,递归地处理这些子任务,然后将各个子任务的结果合并得到复杂任务的结果,这种算法就是所谓的分而治之思想。分治法的一般步骤:分解->求解->合并。比如大数据领域的MapReduce计算框架就是采用了分治的思想,首先将输入切割成标准分片,然后对每个分片经过一系列计算处理,最后合并处理结果并输出。
Fork-Join框架
Fork/Join框架:它的主要作用是将一个复杂任务分割成若干个子任务 ,再将一个个的子任务运算的结果进行join汇总,得到最终的结果。
工作窃取:在实际执行过程中,可能每个子任务的处理速度都不相同,导致出现有的线程会先把自己队列里的任务快速执行完毕,而其他线程对应的队列里还有任务等待处理,工作线程就会尝试去获取其他繁忙的工作线程的任务,如此一来就能够减少线程阻塞或是闲置的时间,提高CPU利用率。
Fork/Join实战-ForkJoinPool
ForkJoinPool是ExecutorService的实现类,也就是说它是一种特殊的线程池。首先看看ForkJoinPool的类图关系。
ForkJoinPool执行任务的对象是ForkJoinTask,它提供在任务中执行fork和join的操作机制。ForkJoinTask提供了2个实现类方便我们使用,我们只需要重写compute方法即可,
- RecursiveAction,用于没有返回结果的任务
- RecursiveTask,用于有返回值的任务
ForkJoinPool使用submit或invoke或execute提交任务,两者的区别是:
- invoke方法:同步执行,调用之后需要等待任务完成,才能执行后面的代码
- submit方法:异步执行,有返回值
- execute方法:异步执行,无返回值
使用join或者get方法来获取当任务返回的计算结果。
基本使用-RecursiveAction
/** * 遍历文件,使用RecursiveAction */public class RecursiveActionDemo { public static class RecursiveActionTask extends RecursiveAction { private File file; public RecursiveActionTask(File file) { this.file = file; } @Override protected void compute() { if (file == null) { return; } if (!file.exists()) { return; } if (file.isDirectory()) { for (File listFile : file.listFiles()) { RecursiveActionTask recursiveActionTask = new RecursiveActionTask(listFile); recursiveActionTask.invoke();// recursiveActionTask.fork();// recursiveActionTask.join(); } } else { System.out.println(Thread.currentThread().getName() + ">>>" + file.getAbsolutePath()); } } } public static void main(String[] args) { String filePath = "C:Program FilesJava"; File file = new File(filePath); RecursiveActionTask task = new RecursiveActionTask(file); ForkJoinPool pool = new ForkJoinPool();// ForkJoinPool pool = ForkJoinPool.commonPool(); pool.invoke(task);// pool.execute(task);// task.join(); System.out.println(">>>>>>>>>>>>>>over<<<<<<<<<<<
基本使用-RecursiveTask
/** * 数组求和,使用RecursiveTask */public class RecursiveTaskDemo { private static class SumTask extends RecursiveTask { private final static int THRESHOLD = 10; private int[] sourceArray; private int left; private int right; public SumTask(int[] sourceArray, int left, int right) { this.sourceArray = sourceArray; this.left = left; this.right = right; } @Override protected Long compute() { //数组一分为2,递归切分,直到小于设定阈值 if (right - left < THRESHOLD) { long count = 0; for (int i = left; i <= right; i++) { count = count + sourceArray[i]; } return count; } else { int mid = (left + right) / 2; SumTask leftTask = new SumTask(sourceArray, left, mid); //注意右边从(mid+1)开始 SumTask rightTask = new SumTask(sourceArray, mid + 1, right); invokeAll(leftTask, rightTask); return leftTask.join() + rightTask.join(); } } } /** * 生成整形数组 * @param length */ private static int[] createArray(int length) { if (length < 0) { return new int[0]; } int[] arr = new int[length]; for (int i = 0; i < length; i++) { arr[i] = i; } return arr; } public static void main(String[] args) { int[] src = createArray(10000000); long start = System.currentTimeMillis(); ForkJoinPool pool = new ForkJoinPool(); SumTask task = new SumTask(src, 0, src.length - 1); pool.invoke(task); System.out.println("sum = " + task.join() + " 耗时:" + (System.currentTimeMillis() - start) + "ms"); }}
ForkJoinPool比较适合计算密集型任务,使用ForkJoinPool并不一定就比单线程要快,实际使用时请综合考虑。