分而治之—Fork-Join

Fork-Join

java下多线程的开发可以我们自己启用多线程,线程池,还可以使用forkjoin,forkjoin可以让我们不去了解诸如Thread,Runnable等相关的知识,只要遵循forkjoin的开发模式,就可以写出很好的多线程并发程序。
Fork-Join使用分治发的思想,将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。常见的归并排序、二分查找、快速排序都是。

Fork-Join原理

图片 1.png

Fork-Join框架就是在必要的情况下,将一个任务进行拆分fork成若干的小任务再将一个小任务运算结果进行join汇总。

工作密取
图片 2.png
    private static class ConsumerAndProducer implements Runnable{  
        private Random random=new Random();  
        private final LinkedBlockingDeque deque;  
        private final LinkedBlockingDeque otherWork;  
        public ConsumerAndProducer(LinkedBlockingDeque deque,LinkedBlockingDeque otherWork){  
            this.deque = deque;  
            this.otherWork = otherWork;  
        }  
        @Override  
        public void run() {  
            //dosomethting
             if(deque.isEmpty()){  
                        if(!otherWork.isEmpty()){  
                            System.out.println("otherWork is run:");
                            otherWork.takeLast().run();;  
                        }  
                          
            }
        }
        
    }

即当前线程的Task已经全部执行完毕,则自动取到其他线程的Task池中的Task执行。ForkJoinPool中维护着多个线程(一般为CPU核数)在不断地执行Task,每个线程除了执行自己职务内的Task之外,还会根据自己工作线程的闲置情况去获取其他繁忙的工作线程的Task,如此一来就能能够减少线程阻塞或是闲置的时间,提高CPU利用率。代码上没有什么好说的,利用双端队列将繁忙的线程的任务拿到自己线程去处理。

Fork-Join实战

图片 3.png

我们要使用ForkJoin框架,必须首先创建一个ForkJoin任务。它提供在任务中执行fork和join的操作机制,通常我们不直接继承ForkjoinTask类,只需要直接继承其子类。

  1. RecursiveAction,用于没有返回结果的任务
  2. RecursiveTask,用于有返回值的任务
    task要通过ForkJoinPool来执行,使用submit 或 invoke 提交,两者的区别是:invoke是同步执行,调用之后需要等待任务完成,才能执行后面的代码;submit是异步执行。

在我们自己实现的compute方法里,首先需要判断任务是否足够小,如果足够小就直接执行任务。如果不足够小,就必须分割成两个子任务,每个子任务在调用invokeAll方法时,又会进入compute方法,看看当前子任务是否需要继续分割成孙任务,如果不需要继续分割,则执行当前子任务并返回结果。使用join方法会等待子任务执行完并得到其结果。
例如我们要给一个文件夹中包含子文件夹下的文件某些文件

public class FindDirsFiles extends RecursiveAction {

    private File path;

    public FindDirsFiles(File path) {
        this.path = path;
    }

    @Override
    protected void compute() {
        List subTasks = new ArrayList<>();

        File[] files = path.listFiles();
        if (files!=null){
            for (File file : files) {
                if (file.isDirectory()) {
                    // 对每个子目录都新建一个子任务。
                    subTasks.add(new FindDirsFiles(file));
                } else {
                    // 遇到文件,检查。
                    if (file.getAbsolutePath().endsWith("txt")){
                        System.out.println("文件:" + file.getAbsolutePath());
                    }
                }
            }
            if (!subTasks.isEmpty()) {
                // 在当前的 ForkJoinPool 上调度所有的子任务。
                for (FindDirsFiles subTask : invokeAll(subTasks)) {
                    subTask.join();
                }
            }
        }
    }

    public static void main(String [] args){
        try {
            // 用一个 ForkJoinPool 实例调度总任务
            ForkJoinPool pool = new ForkJoinPool();
            FindDirsFiles task = new FindDirsFiles(new File("F:/"));

            /*异步提交*/
            pool.execute(task);

            /*主线程做自己的业务工作*/
            System.out.println("Task is Running......");
            Thread.sleep(1);
            int otherWork = 0;
            for(int i=0;i<100;i++){
                otherWork = otherWork + i;
            }
            System.out.println("Main Thread done sth......,otherWork="
                    +otherWork);
            task.join();//阻塞方法
            System.out.println("Task end");
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

我们首先初始化连接池,遍历当前文件下的文件夹存放到subTasks集合中,通过invokeAll方法放入ForkJoinPool中,遍历每个FindDirsFiles的join获得结果。

你可能感兴趣的:(分而治之—Fork-Join)