Fork/Join 框架的基础以及示例

Fork/Join 框架主要由Fork 和 Join 两个操作构成:
        1)Fork操作主要用于对任务和数据进行划分,一般是将一个大问题划分为几个小问题,以期望能够方便地对简化后地问题进行处理
        2)Join操作主要作用于对各个部分地运行结果进行合并,相当于程序中的一个障栅。
        这两个操作与MapReduce中的Map/Reduce操作类似,简而言之,Fork/Join框架是一种设计模式,是一种思想,在其他编程语言中也同样适用。
    Fork/Join 框架实现了任务的定义和任务处理功能的分离,使程序员在现实并行编程的同时,可以更好的集中精力实现业务逻辑相关的内容,这大大降低了并行程序设计的难度。
    在Java中,Fork/Join 框架实现了ExecutorService接口,与任何以一个ExecutorService接口的功能相同的是:Fork/Join会自动地将任务分配给线程池中地线程,并负责线程管理地相关工作。
    与ExecutorService不同地是:Fork/Join使用了工作窃取算法,已经完成自身任务线程可以从其他繁忙地线程中窃取任务来执行,从而保持了线程执行过程中地负载均衡。
    
    Fork/Join 主要用途:Fork/Join框架通常被用于解决那些可以递归地分解为更小地任务地问题。该框架与负载均衡、分治(Divide and Conquer)方法及工作窃取算法(Work-steaing Algorithm)有密切地联系。

    Fork/Join地负债均衡
        负载不均衡会导致一些线程空闲,从而造成资源浪费。常见了负载不均衡地原因是分配给某个线程地任务过多,其他线程都执行完毕,但该线程仍继续执行。
        负载均衡是指在CPU处理器核上运行地线程全部以相同地繁忙程度来工作,理想情况下,所有地线程都都成同样大小的工作量。
        负载均衡有利于加快程序的执行,减少资源浪费,因此我们在设计并行程序时,也应该保证负载均衡,但有些情况的负债均衡任务是由操作系统或JVM虚拟机来完成的。
    Frok/Join的分治方法
        分治方法是简化问题的一种处理方法,该方法的基本思想是将一个复杂的任务分解为若干个小任务,然后分别解决这些小任务。
        使用分治方法的步骤:
            1)分解。将复杂的任务分成小任务,直到把任务分解到容易解决为止。
            2)解决。对已完成分解的小人物进行求解。
            3)j结果合并。如果每个任务都有返回结果,则需要对结果进行合并。
        分治法可以解决合并排序,二分搜索和矩阵相乘问题。
        在使用Fork/Join框架的过程中,首先对ForkJoinTask进行分解,任务分解的数目可以根据问题的特征以及CPU可同时处理的线程数进行设定,然后将分解的任务交给ForkJoinPool处理,最后对处理结果进行收集。
    工作窃取算法:
        工作窃取算法是提高程序性能,保证负债均衡的一种算法。该算法的基本思想是当程序中某些线程做完自身的工作后,去查看其他线程是否还有未处理完成的工作,如果有,则窃取一部分工作来执行,从而帮助那些未完成的
        程序尽快完成他们的工作。
        程序在使用该算法后,一方面,可以保证线程始终处于一种忙碌状态,提高资源的利用率;另一方面,也可以减少其他繁忙线程的处理时间,有助于提高程序的性能。
        从本质上来说,工作窃取算法是一种任务调度方法,尽量使每个线程都能处于忙碌状态。生活中"窃取"是一个编译词,但在这里却有很积极的意义。
        Fork/Join框架与Executor框架的不同之处在于Fork/Join框架采用了工作窃取算法,可以说该算法是Fork/Join框架的核心。在ForKJoinPool中,有一些线程执行任务较快,在做完自己的工作之后,这些线程将尝试发现那些未被执行的任务,
        如果找到,则执行这些任务。
        Fork/Join框架采用双端队列(Deque)作为任务的存储结构,该队列支持后进先出的数据pop和push操作,并且支持先进先出的take操作。由某一工作线程创建的子任务仍然会被加入到线程的队列中,一般采用push操作,工作线程从自己的
        双端队列中取出执行任务,一般采用pop操作,当工作线程需要从其他线程的工作队列中窃取任务时,一般采用take操作。
        线程池中的线程每次是从队列的头部取出任务来执行,当使用使用fork操作产生新任务时,会把新的任务加入到队列的头部,而不像其他线程池加入到尾部,这样可以保证fork出来的新的任务可以尽快得到执行。
        当某个线程执行完自己的任务,而没有其他任务可处理时,就从队列尾部窃取一个任务执行。
    Fork/Join 框架的编程技术
        java中Fok/Join框架比较适于解决那些具有递归性质,可以进行任务分解的程序。
        Fork/Join框架需要对任务进行分解和合并操作,在分解前,首先查看问题的规模是否超过了预设门槛值(Threshold Value),在任务规模不大的情况下,采用串行的解决方式,由于该方式省去了分解和合并的操作有时效果会更好。
        在使用Fork/Join框架时,门槛值通常时人为地进行设定,当问题地规模小于门槛值时,说明没必要采用并行地解决方式,因此更倾向于采用串行执行方式或者采用其他更优化地算法解决;
        而当问题地规模大于门槛值时,采用Fork/Join框架求解。
        使用Fork/Join框架编程模式:如下所示:
        if(问题模式<门槛值){
            //使用串行模式解决或者其他模式解决
        }else{
            //将任务Task尽心分解,分解为若干个小任务 Task1,Task2.......
            //将任务Task1、Task2提交到线程池执行
            //如果任务有返沪结果,收集结果        
        }
    ForkJoinPool 类
        ForkJoinPool类是Fork/Join框架地核心,也是Fork/Join框架执行地入口点,它是实现了接口Executor Service。ForkJoinPool类地任务是负责管理线程,并提供线程执行状态和任务处理地相关信息。
        ForkJoinPool类区别于其他ExecutorService的地方在于该类实现了工作窃取算法,在线程池中的线程总是尝试发现其他可以运行的任务,可以通过相关的方法获取工作窃取的执行情况。
    ForkJoinPool的创建
        ForkJoinPool类从AbstractExecutorService类继承,主要用于处理ForkJoinTask中的任务。
        以下是ForkJoinPool类构造方法的形式:
            1)ForkJoinPool(); //该构造方法将默认生成一个线程池,线程池中可以同时运行的线程数和CPU能够同时运行的最大线程数(可以通过Runtime.getRuntim.availableProcessors()获取)相同。
            2)ForkJoinPool(int parallelism);//用户可以指定线程池中线程的数目。
            //parallelism 指明并行运行的线程数
            //factory 是线程工厂,用于创建新线程
            //handler 用户处理内部出现的异常
            //asyncMode 用于指定ForkJoinPool的工作模式,当为true时,表示工作本地的先进先出模式。 默认为false。对于某些应用来讲,为true比默认的更加合适。
            3)ForJoinPool(int parallelism,ForkJoinWorkThreadFactory factory,Thread.UncaughtExceptionHandler handler,boolean asyncMode);//        
        demo 示例:
            创建一个ForkJoinPool
            ForkJoinPool pool = new ForkJoinPool();
            ForkJoinPool pool = new ForkJoinPool(8);
    ForkJoinPool的使用:
        ForkJoinPool的使用大致可以分为两种,一种时通过invoke、execute和submit执行的任务,另一种时在程序执行过程中通过fork操作来执行的任务。
        ForkJoinPool常用的方法:
            1)invoke(ForkJoinTask<T> task);//是一个同步调用方法,用于处理给定任务,并在处理完毕后返回处理的结果,返回结果的类型由T指定。
            2)invokeAll(Collection<? extends Callable<T> >tasks);//是一个同步调用方法,可以将若干个任务组成的一个集合交给ForkJoinPool执行,任务在继续运行之前会等待子任务的结束,该方法
            返回任务的结果,结果类型由T指定。ForkJoinTask类的该方法是Executor和Fork/Join框架的主要不同之处。
            3)execute(Runnable task);//可以把一个Runnable线程所有代表的任务收纳柜ForkJoinPool中,需要指出的是:ForkJoinPool不会对Runnable对象使用工作窃取算法,该算法只会被应用到ForkJoinTask对象中。
            同样,ForkJoinPool不会对Callable对象使用工作窃取算法。
            4)submit(ForkJoinTask<T> task);//用于把一个任务提交给ForkJoinPool执行,返回ForkJoinTask<T>的结果。
        以上三类执行任务的方法,具体使用如下:
            ------------------------------------------------------------------------------------------------------------------------
                         |    在外部对Fork/Join操作的调用    |    在Fork/Join框架范围内使用
            ------------------------------------------------------------------------------------------------------------------------
            异步执行                 |     execute(ForkJoinTask)    |      ForkJoinTask.fork()
            ------------------------------------------------------------------------------------------------------------------------
            同步执行(等待子任务完成)     |     invoke(ForkJoinTask)    |      ForkJoinTask.invoke()
            ------------------------------------------------------------------------------------------------------------------------
            执行并获得结果         |     submit(ForkJoinTask)    |      ForkJoinTask.fork()
            ------------------------------------------------------------------------------------------------------------------------
    Fork/Join框架中的任务
        ForkJoinTask类是所有的在Fork/Join框架中执行任务的基类,提供了一系列机制来实现Fork和Join操作,该类由两个子类,分别是RecursiveAction和RecursiveTask,从RecursiveAction类继承类的子类方法一般没有返回值,
        从RecursiveTask类继承的子类则由返回值。
        public abstract class ForkJoinTask<?> extends Object implements Future<V>,Serializable
        ForkJoinTask 类实现了接口Serializable 和 Future<V>,所以一般在子类加入serialVersionUID变量定义。 如:
        private static final long SerialVersionUID = 1L;
    Fork/Join 任务的创建
        在创建任务时,最好不要从ForkJoinTask类直接继承,而是从该类的子类RecursiveAction或RecursiveTask继承。
        1)从RecursiveAction 继承承建任务
            从RecursiveAction类继承的子类方法一般没有返回值。继承后的新类需要重写该类的computer()方法。
            computer()方法的形式如下:
            @Override
            public void computer(){
                //方法体
            }        
        isDone()方法,用于判断任务是否完成。
        cancel(booleanmayInterrupteIfRunning)用于取消一个任务的执行。
        当任务提交到ForkJoinPool后获得运行的机会。
        
        demo 示例:    
        使用Fork/Join 框架对班级的人数进行更新。当需要更新班级的人超过10时,更新任务进行细分。

        分析:Fork/Join 框架的门槛 10
        
        //班级的对象类
        public class ClassInfo{
                
            private String name;
            private int number;
            
            public ClassInfo( String name,int number){
                this.name = name;
                this.number = number;
            }
            public String getName(){
                return name;
            }
            public int getNumber(){
                return number;
            }
            public void setNumber(int number){
                this.number = number;
            }    
        }
        /更新操作的任务类
        public class UpdateTask extends RecursiveAction{
            
            private static final long serialVersionUID =1L;
            private List<ClassInfo> classInfos;
            private int start;
            private int end;
            private int increment;
            private int nthreads;
            private int threshold;
        
            public UpdateTask(List<ClassInfo> classInfos,int start,int end,int increment,int nthreads,int threshold){
                this.classInfos = classInfos;
                this.start = start;
                this.end = end;
                this.increment = increment;
                this.nthreads = nthreads;
                this.threshold = threshold;
            }
            @Override
            public void compute(){
                //门槛 串行
                if(end-start<=threshold | threshold ==1){
                    updateSequential();
                }else{
                    UpdateTask []  tasks = new UpdateTask[nthreads];
                    int []  data = new int[nthreads+1];
                    int segment = (end -start+nthreads-1)/nthreads;
                    for(int i=0;i<=nthreads;i++){
                        data[i] = start+segment*i;
                        if(data[i]>end){
                            data[i] = end;
                        }
                    }
                    int mid = (end=start)/2;
                    for(int i=0;i<nthreads;i++){
                        tasks[i] = new UpdateTask(classInfos,data[i],data[i+1],increment,nthreads,threshold);
                    }
                    invokeAll(tasks);
                }
            }
            public void updateSequential(){
                for(int i = start;i<end;i++){
                    ClassInfo  classInfo = classInfos.get(i);
                    classInfo.setNumber(classInfo.getNumber()+increment);
                }
            }
        }
        //启动测试类
        public class Index{
            public static void main(String [] args ){
                int nthreads = Runtime.getRuntime().availableProcessors();
                int threshold = 10;
                int increment = 5;
                int baseNum = 50;
                int size = 10000;
                List<ClassInfo> classInfos = new ArrayList<ClassInfo>();
                for(int i=0;i<size;i++){
                    ClassInfo classInfo = new ClassInfo("班级"+i,baseNum);
                    classInfos.add(classInfo);
                }
                ForkJoinPool pool = new ForkJoinPool();
                UpdateTask updateTask = new UpdateTask(classInfos,0,classInfos.size(),increment,nthreads,threshold);
                pool.execute(updateTask);
                do{
                    System.out.printf("类Index:并行度:%d\n",pool.getParallelism());
                    System.out.printf("类Index:活动线程数:%d\n",pool.getActiveThreadCount());
                    System.out.printf("类Index:任务数:%d\n",pool.getQueuedTaskCount());
                    System.out.printf("类Index:%d\n",pool.getStealCount());
                    try{
                        TimeUnit.SECONDS.sleep(1);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }while(!updateTask.isDone());
                if(validate(classInfos,baseNum+increment)){
                    System.out.println("所有班级更新完毕!");
                }else{
                    System.out.println("Something wrong happend.");
                }
            }
            public static boolean validate(List<ClassInfo> classInfos, int total){
                boolean pass = true;
                for(ClassInfo info:classInfos){
                    if(info.getNumber()!=total){
                        pass = false;
                    }
                }
                return pass;
            }    
        }

运行结果:

类Index:并行度:8
类Index:活动线程数:0
类Index:任务数:0
类Index:41
所有班级更新完毕!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章