java并发编程-----Fork-Join分治

一、Fork-Join框架

forkjoin即分而治之,在几大排序算法中,快速排序、归并排序、二分查找均用到了分治的思想,即将一个大问题,逐一分解为一个个小问题,将各个子问题的解最终合并为最终解。这里也是如此,将一个大任务分解成若干个小任务(fork),再将这若干的小任务的执行结果进行合并(join)。

二、工作密取

fork-join采用工作密取的方式实现线程的工作负载。forkjoinPool中维护了多个线程执行分下的小任务,当当前线程的任务结束后,会自动获取其他线程的任务继续执行,同时也会根据当前工作线程的空闲情况去获取任务较多的线程的task,以达到线程间的平衡,提供CPU利用率。

三、fork-join实现

类结构

java并发编程-----Fork-Join分治_第1张图片

方法

  1. fork()
    将任务放入到工作队列中,交由线程通过submit或execute执行任务
    forkjoin框架提供了两个子类供我们继承使用。
  2. join()
    等待任务执行结束并返回执行结果,
    分别是:
    1.RecursiveAction
    此类主要用于没有返回值的任务。
    2.RecursiveTask
    此类主要用于有返回值的任务
    3.CountedCompleter
    此类主要用于无返回值任务,完成任务后可以触发回调。
    两个类都提供了compute的方法,用于生成、调用子任务,完成分治运算。在compute方法中,需要判断当前任务是否达到设置的阈值,如果小于该值,则直接执行,否则,将该任务分割成两个小任务,再让两个小任务调用invokeAll方法,会再次进入compute方法,重复以上工作。最后使用join方法等到子任务执行结束获取其运行的结果,合并即可。

这里使用invokeAll而不是用invoke主要是因为,如果使用invoke会使任务分离开,两个任务中只工作一个任务,而使用invokeAll可以让两个任务同时工作,提高效率。

  • fork-join的任务是通过ForkJoinPool池来执行,其提供了两种提交方式,submit和invoke,其中,submit是异步提交,不需要等待任务运行完毕即可运行sumbit之后的代码;相反,invoke是同步执行,必须等待任务完成才会执行invoke之后的代码。

四、使用Foke-Join实现数组求和

4.1创建求和任务类

自定义阈值,当任务低于该阈值时直接执行,否则进行分割,任务执行完毕后返回子任务合并的结果。

public class AddTask extends RecursiveTask<Integer> {
    /**
     * 阈值
     */
    private static final int THRESHOLD=10;
    private int[] nums;
    //左边界
    private int left;
    //右边界
    private int right;

    public AddTask(int[] nums, int left, int right) {
        this.nums = nums;
        this.left = left;
        this.right = right;
    }

    /**
     * 生成随机数组
     * @return
     */
    public static int[] randomNum(){
        Random r = new Random();
        int[] nums = new int[100];
        for(int i=0;i<100;i++){
            nums[i] =  r.nextInt(100);
        }
        return nums;
    }
    @Override
    protected Integer compute() {
        if (right-left<THRESHOLD){
            //任务在阈值范围内,符合要求,执行执行任务
            int sum=0;
            for (int i = left; i <= right; i++) {

                try {
                    TimeUnit.MILLISECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                sum+=nums[i];
            }
            return sum;
        }else {
            //任务太大,需要分割成小任务
            int mid=(left+right)/2;
            AddTask leftTask=new AddTask(nums,left,mid);
            AddTask rightTask=new AddTask(nums,mid+1,right);
            invokeAll(leftTask,rightTask);
            //返回子任务合并的结果
            return leftTask.join()+rightTask.join();
        }
    }
}

4.2创建Add类调用任务类

 */
public class Add {
    public static void main(String[] args) {
        ForkJoinPool forkJoinPool=new ForkJoinPool();

        int[] nums = AddTask.randomNum();
        System.out.println("待求和数组:"+ Arrays.toString(nums));
        //创建求和任务
        AddTask addTask=new AddTask(nums,0,nums.length-1);
        forkJoinPool.invoke(addTask);
        System.out.println("求和中。。");


        System.out.println("和为:"+addTask.join());
    }
}

结果:
待求和数组:[34, 38, 71, 98, 38, 51, 35, 67, 59, 58, 33, 64, 65, 40, 30, 49, 67, 15, 4, 3, 5, 10, 94, 17, 24, 43, 94, 52, 53, 81, 7, 70, 25, 90, 96, 3, 84, 73, 54, 11, 42, 97, 18, 11, 95, 28, 14, 74, 95, 91, 53, 48, 69, 71, 91, 64, 25, 72, 3, 74, 77, 90, 22, 36, 77, 86, 41, 53, 92, 15, 30, 5, 51, 86, 60, 49, 76, 91, 15, 38, 55, 37, 24, 58, 53, 58, 75, 77, 34, 41, 88, 50, 91, 25, 56, 83, 14, 3, 75, 7]
求和中。。
和为:5134

而如果使用submit提交,将数组大小改为10000,就可看到求和中一句话是在任务执行完毕后才会打印,体现同步性。

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