Java7:Fork-Join框架

Fork-Join框架(Java 7)

       Java在JDK7之后加入了并行计算的框架Fork/Join,Fork/Join采用的是分治法,Fork 是将一个大任务拆分成若干个子任务,子任务分别去计算,而 Join 是获取到子任务的计算结果,然后合并,这个是递归的过程。子任务被分配到不同的核上执行时,Fork-Join框架很适合使用于解决运行在多核心处理器上的并发问题;

总的来说Fork-Join模式的核心是分治,Fork分解任务,Join收集数据;
 

 
Fork/Join框架的核心类

Fork-Join框架有几个主要的核心类:

ForkJoinPool类:线程池类,负责运行子任务,继承自ExecutorService,可按照Java5的ExecutorServic的方式去调用它;
ForkJoinPool 常用构造器:
    
    
  1. ForkJoinPool(int parallelism); //创建一个parallelism个并行线程的ForkJoinPool
  2. ForkJoinPool(); // 以Runtime.avaliableProcessors()方法的返回值作为并行线程数量参数
ForkJoinPool提交线程的主要方法: submit,execute,invoke;
  客户端PorkJoinPool线程池提交任务 ForkJoinTask任务类内部调用任务
异步执行 execute(ForkJoinTask) ForkJoinTask.fork
等待获取结果 invoke(ForkJoinTask) ForkJoinTask.invoke
执行,获取Future

submit(ForkJoinTask)

ForkJoinTask.fork(ForkJoinTask are Futures)

ForkJoinTask类:任务类的默认实现,实际一般使用的它的两个默认实现:
RecurisiveAction :没有返回值的任务类抽象类。
RecurisiveTask :带有返回值的任务类抽象类。
ForkJoinTask的两个主要方法 fork 提交子任务,join 获取子任务执行结果




※Fork-Join 框架任务的执行由ForkJoinTask类的对象之外,还可以使用一般的Callable和Runnable接口来表示任务。


异常处理

ForkJoinTask在执行的时候可能会抛出异常,但是没办法在主线程里直接捕获异常,所以ForkJoinTask提供了 i sCompletedAbnormally() 方法来检查任务是否已经抛出异常或已经被取消了,并且可以通过ForkJoinTask的   getException  方法获取异常。使用如下代码:
     
     
  1. if(task.isCompletedAbnormally()) {
  2. System.out.println(task.getException());
  3. }
getException方法返回Throwable对象,如果任务被取消了则返回CancellationException。如果任务没有完成或者没有抛出异常则返回null。


代码示例

示例1: RecursiveTask 有返回值任务类
简要模拟 Java 8 parallelStream并行数据流的sum方法(为了便于理解使用二分法实现分割,JDK中使用的是更加复杂的分割算法); 使用 RecursiveTask 分治有返回值的线程任务:并行计算List的sum值,在将其分割到小于阀值后再由子线程进行计算; 
   
   
  1. //子任务类
  2. public static class SumTask extends RecursiveTask<Long> {
  3. private static final int THRESHOLD = 100; //规定分割阀值
  4. private List<Long>list;
  5. private long low;
  6. private long high;
  7. public SumTask(List<Long> list,long low,long high){
  8. this.list = list;
  9. this.low = low;
  10. this.high = high;
  11. }
  12. @Override //覆盖compute接口,规定分治策略(采用二分法)
  13. protected Long compute() {
  14. long sum = 0;
  15. if(high - low +1 <= THRESHOLD){
  16. sum = list.stream().skip(low).limit(high-low+1).mapToLong(x->x).sum(); //流长度小于阈值直接计算sum
  17. }else{ //流长度大于阈值,对流进行分治
  18. long mid = (low + high) /2;
  19. SumTask leftSubTask = new SumTask(list,low,mid);
  20. SumTask rightSubTask = new SumTask(list,mid+1,high);
  21. //分发运行子线程
  22. leftSubTask.fork();
  23. rightSubTask.fork();
  24. //或者使用 invoke(lefSubTask,rightSubTask);代替以上任务提交的两行;
  25. //获取子线程结果
  26. sum = leftSubTask.join() + rightSubTask.join();
  27. //或者以如下获取结果
  28. /*try {
  29. sum = leftSubTask.get() + rightSubTask.get();
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. } catch (ExecutionException e) {
  33. e.printStackTrace();
  34. }*/
  35. }
  36. return sum;
  37. }
  38. }
  39. //客户端线程池部分
  40. public static void main(String[] args){
  41. SumTask sumTask = new SumTask(list,0,list.size()-1); //创建并行运算任务
  42. ForkJoinPool forkJoinPool = new ForkJoinPool(); //创建ForkJoinPool线程池
  43. forkJoinPool.submit(sumTask); //向ForkJoinPool提交任务
  44. forkJoinPool.shutdown(); //开始执行线程池
  45. long result = sumTask.join(); //获取任务结果
  46. System.out.println(result == rightResult); //验证结果是否正确
  47. }

示例2:RecursiveAction 无返回值任务类
使用 RecursiveTask 分治有返回值的线程任务:并行将List中元素自增,在将其分割到小于阀值后再由子线程进行;
   
   
  1. //子任务类
  2. public static class PrintTask extends RecursiveAction {
  3. private static final int THRESHOLD = 20; //规定分割阀值
  4. private int[] array;
  5. private int low;
  6. private int high;
  7. public PrintTask(int[] array, int low,int high){
  8. this.array = array;
  9. this.low = low;
  10. this.high = high;
  11. }
  12. @Override
  13. protected void compute() {
  14. if(high - low + 1 < THRESHOLD){
  15. for(int i=low;i<=high;i++){
  16. array[i] = array[i] + 1;
  17. }
  18. }else{
  19. int mid = (high + low) / 2;
  20. PrintTask leftSubTask = new PrintTask(array,low,mid);
  21. PrintTask rightSubTask = new PrintTask(array,mid+1,high);
  22. leftSubTask.fork();
  23. rightSubTask.fork();
  24. // 或者使用 invokeAll(leftSubTask,rightSubTask); 代替以上两行的任务提交
  25. }
  26. }
  27. }
  28. //ForkJoinPool线程池提交
  29. public static void main(String[] args){
  30. int[] array = new int[1000]; //创建测试数据
  31. PrintTask printTask = new PrintTask(array,0,array.length-1);
  32. ForkJoinPool forkjoinPool = new ForkJoinPool();
  33. forkjoinPool.execute(printTask);
  34. forkjoinPool.shutdown();
  35. try {
  36. //等待forkjoinPool中的所有线程都结束或参数时间后,再执行本线程,
  37. forkjoinPool.awaitTermination(30,TimeUnit.SECONDS);
  38. System.out.println(Arrays.stream(array).allMatch(x->x == 1)); //验证结果
  39. } catch (InterruptedException e) {
  40. e.printStackTrace();
  41. }
  42. }


ForkJoinPool 运行  RecursiveTask 型任务时,在提交任务后可以使用以下两种方法阻塞客户端代码,直到 ForkJoinPool 中的任务类执行完毕,可以不用自己手动编写阻塞代码:
   
   
  1. ForkJoinPool forkjoinPool = new ForkJoinPool();
  2. forkjoinPool.execute(new Task());
  3. forkjoinPool.shutdown();
  4. //method 1 : 阻塞客户端线程直到 forkjoinPool中的任务执行完毕,或阻塞时间达到参数值,
  5. 会抛出InterruptedException异常;
  6. forkjoinPool.awaitTermination(60,TimeUnit.SECONDS);
  7. //method 2: 阻塞客户端线程直到 forkjoinPool中的任务执行完毕
  8. forkjoinPool.awaitQuiescence()



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值