jdk1.7中提供了Fork/Join并行执行任务框架,主要作用就是把大任务分割成若干个小任务,再对每个小任务得到的结果进行汇总。
正常情况下,一些小任务我们可以使用单线程递归来实现,但是如果要想充分利用CPU资源,就需要把一个任务分成若干个小任务,并行执行了,这就是分治编程。
在JDK中,并行执行框架Fork-Join使用了“工作窃取(work-stealing)”算法。
JDK1.7中实现分治编程思路:
使用 ForkJoinPool 类提供了一个任务池。
具体执行任务需要靠 ForkJoinTask 类,而 ForkJoinTask 是抽象类,故使用该类的3个子类 CountedCompleter、RecursiveAction、RecursiveTask 来实现具体的功能。
其中,RecursiveAction 执行的任务具有无返回值,且仅执行一次;RecursiveTask 执行任务可以通过方法 join() 或者 get() 取得方法返回值。
补充, join() 和 get() 的区别:两个方法都可以获得计算后的结果值,区别是在子任务报异常时,get() 的异常可以在main主线程中进行捕获;而 join() 的异常会直接抛出。
看例子(求和:1+2+3+...+9999+10000):
package com.cd.thread; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; import java.util.concurrent.RecursiveTask; public class ForkJoinTest { public static void main(String[] args) { final SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); System.out.println("start-time:" + sf.format(new Date())); addTest(); // for循环 System.out.println("end-time:" + sf.format(new Date())); System.out.println("start-time:" + sf.format(new Date())); forkJoinTest();// 分治求和 System.out.println("end-time:" + sf.format(new Date())); } private static void addTest() { int beginNum = 1, endNum = 10000, val = 0; for (int i = beginNum; i <= endNum; i++) { val += i; } System.out.println("for循环结果:" + val); } public static void forkJoinTest() { MyRecursiveTask task = new MyRecursiveTask(1, 10000); ForkJoinPool pool = new ForkJoinPool(); ForkJoinTask<Integer> fjTask = pool.submit(task); try { System.out.println("分治求和结果:" + fjTask.get()); } catch (Exception e) { e.printStackTrace(); } } private static class MyRecursiveTask extends RecursiveTask<Integer> { private Integer beginNum; private Integer endNum; private MyRecursiveTask(Integer beginNum, Integer endNum) { this.beginNum = beginNum; this.endNum = endNum; } @Override protected Integer compute() { if ((endNum - beginNum) > 500) { int middleNum = (endNum + beginNum) / 2; MyRecursiveTask task1 = new MyRecursiveTask(beginNum, middleNum); MyRecursiveTask task2 = new MyRecursiveTask(middleNum + 1, endNum); this.invokeAll(task1, task2); // return task1.join() + task2.join(); Integer num1 = 0, num2 = 0; try { num1 = task1.get(); num2 = task2.get(); } catch (Exception e) { e.printStackTrace(); } return num1 + num2; } else { Integer val = 0; for (int i = beginNum; i <= endNum; i++) { val += i; } return val; } } } }
从结果看,分治编程不一定会比单线程快,所以在用分治编程的时候,需要一定的测试才行。
而分治编程也有运用的领域,比如遍历一个目录及其子目录,处理一个树形结构算法问题。
在写代码的时候,会发现分治的代码看起来像递归,但是其实它们是并行执行的。
关于 ForkJoinPool 的 api,建议用到的时候,去看文档吧,看文档也是一种能力,也是一种技巧。