JAVA并发编程:线程并发工具类Fork-Join原理分析及实战

1、Fork-Join

  Java下多线程的开发我们可以自己启用多线程、线程池,还可以使用ForkJoin。 ForkJoin 可以让我们不去了解诸如 Thread、Runnable 等相关的知识,只要遵循 ForkJoin 的开发模式,就可以写出很好的多线程并发程序。

2、Fork-Join体现了分而治之

  什么是分而治之?一种设计思想、策略。十大计算机经典算法:快速排序、堆排序、归并排序、二分查找、线性查找、深度优先、广度优先、Dijktra、动态规划、朴素贝叶斯分类。其中有三个就属于分而治之的设计思想,分别是快速排序、归并排序、二分查找。

  分而治之的设计思想是:将一个难以解决的问题,拆分成一些规模较小的问题,以便各个击破,达到分而治之的目的。

3、Fork-Join原理

  在必要的情况下,将一个大任务,进行拆分(Fork)成若干个小任务,直到不可再拆时,将一个个小任务运算结果进行汇总(Join),最终达到想要的结果,如下图所示。
​​​​JAVA并发编程:线程并发工具类Fork-Join原理分析及实战_第1张图片

4、Fork-Join实战

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

  • RecursiveAction,用于没有返回结果的任务
  • RecursiveTask,用于有返回值的任务

  Task 要通过 ForkJoinPool 来执行,使用 invoke或者submit或者execute 提交,三者的区别是:invoke 是同步执行,调用之后需要等待任务完成,才能执行后面的代码; submit和execute 是异步执行。 join()和 get() 方法当任务完成的时候返回计算结果。
  任务继承RecursiveAction或者RecursiveTask类,重写父类的compute()方法,我们自己的业务写在compute方法里即可,首先需要判断任务是否足够小,如果足够小就直接执行任务。如果不足够小,就必须分割成两个子任务,每个子任务在调用invokeAll 方法时,又会进入 compute 方法,看看当前子任务是否需要继续分割成孙任务,如果不需要继续分割,则执行当前子任务并返回结果。使用 join 方法会等待子任务执行完并得到其结果。

  • Fork/Join的同步用法同时演示返回结果值:统计整形数组中所有元素的和
package cn.lspj.ch2.forkjoin.sum;



import java.util.Random;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

/**
 * 类说明:统计整形数组中所有元素的和
 */
public class SumToForkJoin extends RecursiveTask<Integer> {

    //数组长度
    public static final int ARRAY_LENGTH = 4000;
    /*阈值*/
    private final static int THRESHOLD = ARRAY_LENGTH / 10;
    private int[] array;
    private int fromIndex;
    private int toIndex;

    public SumToForkJoin(int[] array, int fromIndex, int toIndex) {
        this.array = array;
        this.fromIndex = fromIndex;
        this.toIndex = toIndex;
    }

    @Override
    protected Integer compute() {
        // 判断任务的大小是否合适,当数组长度小于设定的某个阈值时,求该数组的和
        if (toIndex - fromIndex < THRESHOLD) {
            int count = 0;
            for (int i = fromIndex; i < toIndex; i++) {
                count += array[i];
            }
            return count;
        }
        // 数组长度不满足设定的阈值,继续拆分数组
        int mid = (fromIndex + toIndex) / 2;
        SumToForkJoin left = new SumToForkJoin(array, fromIndex, mid);
        SumToForkJoin right = new SumToForkJoin(array, mid + 1, toIndex);
        // 调用invokeAll方法继续执行compute方法
        invokeAll(left, right);
        return left.join() + right.join();
    }

    /**
     * 随机生成一个数组
     *
     * @return
     */
    private static int[] makeArray() {
        //new一个随机数发生器
        Random r = new Random();
        int[] result = new int[ARRAY_LENGTH];
        for (int i = 0; i < ARRAY_LENGTH; i++) {
            //用随机数填充数组
            result[i] = r.nextInt(ARRAY_LENGTH * 3);
        }
        return result;

    }

    public static void main(String[] args) {
        // new 出一个pool实例,用来执行task任务
        ForkJoinPool pool = new ForkJoinPool();
        // 生成一个随机值的数组
        int[] array = makeArray();
        // new 出一个task任务,交给pool去执行
        SumToForkJoin task = new SumToForkJoin(makeArray(), 0, array.length);
        // 调用pool的invoke方法,同步执行
        pool.invoke(task);
        // 调用task的join方法,获取数组求和后的值,并输出
        System.out.println("count=" + task.join());

    }

}

执行结果如下:
JAVA并发编程:线程并发工具类Fork-Join原理分析及实战_第2张图片

  • Fork/Join的异步用法同时演示不要求返回值:遍历指定目录(含子目录)寻找指定类型文件
package cn.lspj.ch2.forkjoin;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;

/**
 *类说明:遍历指定目录(含子目录)找寻指定类型文件
 */
public class FindDirsFilesToForkJoin extends RecursiveAction {

    private File path;

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

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

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

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

            /*异步提交*/
            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();
        }
    }
    
}

执行结果如下:
JAVA并发编程:线程并发工具类Fork-Join原理分析及实战_第3张图片
  从执行结果中不难看出,使用execute方法异步调用执行task,主线程不会等待子线程先执行,而是继续完成主线程的业务工作。


备注:博主微信公众号,不定期更新文章,欢迎扫码关注。
JAVA并发编程:线程并发工具类Fork-Join原理分析及实战_第4张图片

你可能感兴趣的:(Java,并发编程)