Java线程--ForkJoin框架

本文详细介绍了Java的ForkJoin框架,包括其简介、核心类如ForkJoinPool、RecursiveTask和RecursiveAction,以及ForkJoin的工作原理。通过示例展示了如何使用ForkJoinPool进行任务提交和执行,对比了ForkJoinPool与ThreadPoolExecutor的差异。此外,还探讨了ForkJoin的同步执行、异步执行以及应用场景和注意事项。
摘要由CSDN通过智能技术生成

ForkJoin框架

目录

ForkJoin框架

ForkJoin简介

ForkJoin核心类

ForkJoinPool线程池

RecursiveTask有返回值任务​

 RecursiveAction无返回值任务​

ForkJoin原理

fork

join

submit

ForkJoin示例

原始for-loop求和

ForkJoinPool线程池示例 

ThreadPoolExecutor线程池示例

ForkJoin的同步执行

ForkJoin的异步执行 

ForkJoin应用场景

ForkJoin注意事项

ForkJoin的执行效率

ForkJoin的守护线程

ForkJoin异常处理 


 


开始之前,请死记: 

我们使用ForkJoin框架时,我们的主要业务逻辑都是:继承RecursiveTask或者RecursiveAction,业务核心逻辑fork/join叉分合并写在compute()方法内,伪码如下:

class MyTask extends RecursiveTask<T>{

       public T compute(){

             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提交待执行(fork提交:fork与compute的相互递归调用)
               *     子任务执行结果进行join合并
               *     返回合并结果给父任务
               */

               /**
               * 简要说明fork和compute是怎么相互递归调用的
               *
               * @1:fork提交新任务,新任务压入线程池管理的队列workQueue之中
               * @2:新任务由线程池调度工作线程来执行,工作线程LIFO或FIFO机制来执行新任务
               * @3:新任务执行时,会调用exec(){compute();}方法
               * @4:我们自定义的compute(){newChildTask.fork();}方法内又会形成新任务进行fork
               * @5:进行@1-->@4的递归处理,直至新任务足够体量小时不用叉分可直接执行
               */ 
 

       }

}

ForkJoin简介

 JAVA 1.7引入了ForkJoin框架,它是一个并发执行任务的框架。Fork/Join Pool采用优良的设计、代码实现和硬件原子操作机制等多种思路保证其执行性能。其中包括(但不限于):计算资源共享、高性能队列、避免伪共享、工作窃取机制等。Doug Lea关于ForkJoin的论文中给出了这样一段伪代码:

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

ForkJoin的基本思想【分而治之】当任务足够小的时候直接解决它,否则将任务分割成可以独立解决的部分,小的任务解决后再将它们合并,得到大的任务结果。看下示例图,更加清晰的理解它:

forkjoin

注:ForkJoin是一个单机框架,类似的分布式的框架有Hadoop这类的,它们的计算模型是MapReduce,体现了和ForkJoin一样的思想-分而治之。

ForkJoin核心类

  • ForkJoinPool :线程池,实现了ExecutorService接口和工作窃取算法,用于线程调度与管理

  • ForkJoinTask :任务,提供fork()方法join()方法通常不直接使用,而是使用以下子类:

RecursiveAction :无返回值的任务,通常用于只fork不join的情形

RecursiveTask :有返回值的任务,通常用于fork+join的情形

  • ForkJoinWorkerThread : 是 ForkJoinPool 内的 worker thread,被线程池调度,用于执行 ForkJoinTask
  • WorkQueue : 存放ForkJoinTask的队列,有若干个队列

总体来说:ForkJoin框架有雷同线程池Executor框架的设计思路,关于Executor线程池请参考我的博客Executor线程池原理。ForkJoinPool为管家,内部管理了一堆工作线程ForkJoinWorkerThread,和若干任务队列WorkQueue(WorkQueue中存放着待执行的一堆任务ForkJoinTask)。ForkJoinTask是抽象的父类,现实中,我们的程序一般都是继承它的子类RecursiveAction(不需要任务有返回值)或者RecursiveTask(需要利用任务的返回值)即可。


接下来我们主要介绍下,ForkJoinPool线程池、RecursiveTask有返回值任务、RecursiveAction无返回值任务

搞懂了RecursiveTask,就必然一定掌握了RecursiveAction无返回值任务。

ForkJoinPool线程池

在ForkJoinPool主类的注释说明中,有这样一句话:
A static commonPool() is available and appropriate for most applications. The common pool is used by any ForkJoinTask that is not explicitly submitted to a specified pool.
Using the common pool normally reduces resource usage (its threads are slowly reclaimed during periods of non-use, and reinstated upon subsequent use).
中文翻译:ForkJoinPool类有一个静态方法commonPool(),适用于绝大多数的应用系统场景。公共池适用于任何类型的任务,只要这个任务不是必须提交给特定的池。使用commonPool通常可以帮助应用程序中多种需要进行归并计算的任务共享计算资源,从而使后者发挥最大作用(ForkJoinPools中的工作线程在闲置时会被缓慢回收,并在随后需要使用时被恢复)。

而这种获取ForkJoinPools实例的方式,才是Doug Lea推荐的使用方式 。 例如:

ForkJoinPool fjp = ForkJoinPool.commonPool(); //获得线程公共池

Future<T> result = fjp.invoke(task); //公共池内提交一个任务并等待执行结果

在JDK8当中,对ForkJoin进行了增强,可以将上面两句编写成一句代码即可:

Future<T> result = task.invoke(); //等价于上面两句代码


RecursiveTask有返回值任务这里写图片描述

/**
* 截取的部分源码:
*
* compute()方法是核心业务逻辑,我们会在此处编写fork/join的叉分/合并递归的逻辑
*
* 之所以能递归,是因为执行任务时会调用exec(){result = compute();}方法
*              而我们在compute()方法中会编写叉分大任务为小任务,然后fork();而fork()方法的内部会
*              把新叉分的子任务提交给线程池管理的workQueue,待工作线程执行,工作线程一旦执行该子
*              任务时,又会调用exec()方法,又会执行compute()方法,于是这样就构成了递归调用。
*
*递归总结:fork()和compute()递归调用【compute()中调用fork(),fork()中又调用exec(){compute();}】
*/
public abstract class RecursiveTask<V> extends ForkJoinTask<V> {
    private static final long serialVersionUID = 5232453952276485270L;

    /**
     * The result of the computation.
     */
    V result;

    /**
     * The main computation performed by this task.
     * @return the result of the computation
     */
    protected abstract V compute();

    public final V getRawResult() {
        return result;
    }

    protected final void setRawResult(V value) {
        result = value;
    }

    /**
     * Implements execution conventions for RecursiveTask.
     */
    protected final boolean exec() {
        result = compute();
        return true;
    }

}

一般我们编写代码,都是继承RecursiveTask<T>类,实现compute()方法。在compute()方法内部,编写fork/join叉分/合并逻辑。举例如下:

/**
* 以下代码的compute()方法内部的fork/join逻辑,用伪码表示:就是Doug Lea大师的论文伪码
* 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
*/

class Sum extends java.util.concurrent.RecursiveTask<Integer> {

        private static final int THRESHOLD = 20; //叉分临界值(假定每任务体量20个元素合适)

        int id;
        public Sum(int id) {
            this.id = id;
        }

 
        @Override

        protected Integer compute() {

            int allsum = 0;

	    int currentVolume 
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值