• (五) 线程并发工具类之Fork-Join


    归并排序 概念及实现

    归并排序
    https://blog.csdn.net/k_koris/article/details/80508543

    归并排序是建立在归并操作上的一种有效的排序算法。
    该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
    若将两个有序表合并成一个有序表,称为2-路归并,与之对应的还有多路归并。
    对于给定的一组数据,利用递归与分治技术将数据序列划分成为越来越小的半子表,在对半子表排序后,再用递归方法将排好序的半子表合并成为越来越大的有序序列。
    注意
    为了提升性能,有时我们在半子表的个数小于某个数(比如15)的情况下,对半子表的排序采用其他排序算法,比如插入排序。

    Java实现

    package com.monco.ch2.forkjoin;
    
    import java.util.Arrays;
    
    /**
     * @author monco
     * @data 2020/5/26 11:15
     * @description :
     */
    public class MergeSort {
        public static void main(String[] args) {
            int[] arr = {3, 2, 5, 6, 8, 1, 9, 0, 4, 7};
            sort(arr);
            System.out.println(Arrays.toString(arr));
        }
    
        public static void sort(int[] arr) {
            //在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
            int[] temp = new int[arr.length];
            sort(arr, 0, arr.length - 1, temp);
        }
    
        private static void sort(int[] arr, int left, int right, int[] temp) {
            if (left < right) {
                int mid = (left + right) / 2;
                //左边归并排序,使得左子序列有序
                sort(arr, left, mid, temp);
                //右边归并排序,使得右子序列有序
                sort(arr, mid + 1, right, temp);
                //将两个有序子数组合并操作
                merge(arr, left, mid, right, temp);
            }
        }
    
        private static void merge(int[] arr, int left, int mid, int right, int[] temp) {
            //左序列指针
            int i = left;
            //右序列指针
            int j = mid + 1;
            //临时数组指针
            int t = 0;
            while (i <= mid && j <= right) {
                if (arr[i] <= arr[j]) {
                    temp[t++] = arr[i++];
                } else {
                    temp[t++] = arr[j++];
                }
            }
            //将左边剩余元素填充进temp中
            while (i <= mid) {
                temp[t++] = arr[i++];
            }
            //将右序列剩余元素填充进temp中
            while (j <= right) {
                temp[t++] = arr[j++];
            }
            t = 0;
            //将temp中的元素全部拷贝到原数组中
            while (left <= right) {
                arr[left++] = temp[t++];
            }
        }
    }
    
    

    Fork-Join 设计理念及实现

    原理解析
    其核心思想是将一个大的任务,分成若干个小任务,每个小任务分别执行完毕之后,在汇总到大的任务,以达到完成任务的目的。
    分治策略
    对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立与原问题形式相同(子问题相互之间有联系就会变为动态规范算法),递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。
    工作密取
    即当前线程的Task已经全被执行完毕,则自动取到其他线程的Task池中取出Task继续执行。
    ForkJoinPool中维护着多个线程(一般为CPU核数)在不断地执行Task,每个线程除了执行自己职务内的Task之外,还会根据自己工作线程的闲置情况去获取其他繁忙的工作线程的Task,如此一来就能够减少线程阻塞或是闲置的时间,提高CPU利用率。


    Fork-Join 实战

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

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

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

    Fork/Join 实现累加 同步且有返回值实现

    /**
     * @author monco
     * @data 2020/5/26 23:19
     * @description :
     * 继承自 RecursiveTask 用于有返回结果的任务 泛型  中的类型 即为返回数据的类型
     */
    public class ForkJoinSum {
    
        private static class SumTask extends RecursiveTask<Integer> {
    
            /**
             * 定义阈值
             */
            public final static int THRESHOLD = 47;
    
            private int[] src;
            private int fromIndex;
            private int toIndex;
    
            public SumTask(int[] src, int fromIndex, int toIndex) {
                this.src = src;
                this.fromIndex = fromIndex;
                this.toIndex = toIndex;
            }
    
            @Override
            protected Integer compute() {
                /**
                 * 判断数组的长度 是 小于某一个值的时候 进行 sum 操作
                 * 否则将任务拆分
                 */
                if (toIndex - fromIndex < THRESHOLD) {
                    int count = 0;
                    for (int i = fromIndex; i <= toIndex; i++) {
                        SleepTools.ms(1);
                        count = count + src[i];
                    }
                    return count;
                } else {
                    //fromIndex....mid.....toIndex
                    int mid = (fromIndex + toIndex) / 2;
                    SumTask left = new SumTask(src, fromIndex, mid);
                    SumTask right = new SumTask(src, mid + 1, toIndex);
                    //  唤醒任务 没有返回值的
                    invokeAll(left, right);
                    return left.join() + right.join();
                }
            }
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            int[] array = MoncoArray.makeArray();
            /*new出池的实例*/
            ForkJoinPool pool = new ForkJoinPool();
            /*new出Task的实例*/
            SumTask innerFind = new SumTask(array, 0, array.length - 1);
            long start = System.currentTimeMillis();
            pool.invoke(innerFind);
            Integer i = innerFind.get();
            System.out.println("The count is " + innerFind.join() + ":" + i
                    + " spend time:" + (System.currentTimeMillis() - start) + "ms");
        }
    }
    
    

    结果

    单线程 实现累加

    
    /**
     * @author monco
     * @data 2020/5/26 23:19
     * @description : 单线程执行累加
     */
    public class NormalSum {
    
        public static void main(String[] args) {
            int count = 0;
            int[] src = MoncoArray.makeArray();
    
            long start = System.currentTimeMillis();
            for(int i= 0;i<src.length;i++){
                SleepTools.ms(1);
                count = count + src[i];
            }
            System.out.println("The count is "+count
                    +" spend time:"+(System.currentTimeMillis()-start)+"ms");
        }
    }
    

    结果

    生成数组工具类

    /**
     * @author monco
     * @data 2020/5/26 22:24
     * @description : 随机生成数组
     */
    public class MoncoArray {
    
        /**
         * 定义数组长度
         */
        private static final int ARRAY_LENGTH = 40000;
    
        public 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;
        }
    }
    

    Fork/Join 实现文件夹查找 异步无返回值实现

    import java.io.File;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ForkJoinPool;
    import java.util.concurrent.RecursiveAction;
    
    /**
     * @author monco
     * @data 2020/5/26 23:41
     * @description : 查找某一路径下的文件
     * 继承自 RecursiveAction 用于没有返回结果的任务
     */
    public class FindDirsFiles extends RecursiveAction {
    
        private File path;
    
        public FindDirsFiles(File path) {
            this.path = path;
        }
    
        /**
         * 实现 compute 方法
         */
        @Override
        protected void compute() {
            List<FindDirsFiles> 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("exe")) {
                            System.out.println("文件:" + file.getAbsolutePath());
                        }
                    }
                }
                if (!subTasks.isEmpty()) {
                    for (FindDirsFiles subTask : subTasks) {
                        // 在当前的 ForkJoinPool 上调度所有的子任务。
                        subTask.join();
                    }
                }
            }
        }
    
    
        public static void main(String[] args) {
            // 定义一个 forkJoin 池
            ForkJoinPool pool = new ForkJoinPool();
            FindDirsFiles task = new FindDirsFiles(new File("C:/Users/monco/Desktop/Redis-x64-3.0.504"));
            // 异步提交
            pool.execute(task);
    
            /*主线程做自己的业务工作*/
            System.out.println("Task is Running......");
            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");
        }
    }
    
    

    结果

    结论
    使用fork/join 在处理的得当的时机,会大大提升CPU使用效率,从而缩短运行时间。

  • 相关阅读:
    博客模仿
    实体零售的数据分析与信息化之路
    巡店系统如何应用于连锁加盟店管理
    Dynamics CRM 常用的JS
    公众号被动消息回复原理
    form分辨率
    打开新网页 浏览图片
    网页浏览文件
    Model赋值返回json
    装载 反射
  • 原文地址:https://www.cnblogs.com/monco-sxy/p/13138734.html
Copyright © 2020-2023  润新知