【J.U.C-executors】执行器框架——Fork/Join模式简要概述

使用Demo

public class TestForkJoin {

    public static void main(String[] args) {

        int[] array = new int[10000];
        for (int i = 0; i < 10000; i++) {
            array[i] = i;
        }

        ArraySumTask task = new ArraySumTask(array, 0, 9999);

        ForkJoinPool executor = new ForkJoinPool();
        ForkJoinTask future = executor.submit(task);

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}


class ArraySumTask extends RecursiveTask<Long> {

    private final int[] array;
    private final int begin;
    private final int end;

    private static final int THRESHOLD = 100;

    public ArraySumTask(int[] array, int begin, int end) {
        this.array = array;
        this.begin = begin;
        this.end = end;
    }

    @Override
    protected Long compute() {
        long sum = 0;

        if (end - begin + 1 < THRESHOLD) {      // 小于阈值, 直接计算
            for (int i = begin; i <= end; i++) {
                sum += array[i];
            }
        } else {
            int middle = (end + begin) / 2;
            ArraySumTask subtask1 = new ArraySumTask(this.array, begin, middle);
            ArraySumTask subtask2 = new ArraySumTask(this.array, middle + 1, end);

            subtask1.fork();
            subtask2.fork();

            long sum1 = subtask1.join();
            long sum2 = subtask2.join();

            sum = sum1 + sum2;
        }
        return sum;
    }
}

分治算法思想

  • 分治(divide and conquer),也就是把一个复杂的问题分解成相似的子问题,然后子问题再分子问题,直到问题分的很简单不必再划分了。然后层层返回子问题的结果,最终合并返回问题结果。
  • 分治在算法上有很多应用,类似大数据的MapReduce,归并算法、快速排序算法等。JUC中的Fork/Join的并行计算框架类似于单机版的 MapReduce。
  1. 第一个阶段分解任务(fork),把任务分解为一个个小任务直至小任务可以简单的计算返回结果。
  2. 第二阶段合并结果(join),把每个小任务的结果合并返回得到最终结果。
    在这里插入图片描述

Fork/Join框架

Fork/Join框架主要包含两部分:ForkJoinPool、ForkJoinTask。

  1. ForkJoinPool:治理分治任务的线程池。线程池中的每个工作线程都维护着一个工作队列(WorkQueue),这是一个双端队列(Deque),里面存放的对象是任务(ForkJoinTask)。
    ForkJoinPool提供了3类外部提交任务的方法:invoke、execute、submit,它们的主要区别在于任务的执行方式上。
    • invoke:调用线程直到任务执行完成才会返回,也就是说这是一个同步方法,且有返回结果;
    • execute:调用线程会立即返回,也就是说这是一个异步方法,且没有返回结果;
    • submit:调用线程会立即返回,也就是说这是一个异步方法,且有返回结果(返回Future实现类,可以通过get获取结果)。
  2. WorkQueue:保存要执行的 ForkJoinTask
  3. ForkJoinWorkerThread:ForkJoinPool 内的 worker thread,执行 ForkJoinTask,内部有WorkQueue
  4. ForkJoinTask:可以声明以同步/异步方式进行执行的任务,有两个抽象子类:
    • RecusiveAction:有返回值的任务
    • RecusiveTask:没有返回值的任务

ForkJoinPool与ThreadPoolExecutor都是ExecutorService的实现类,其区别在于?

  • ThreadPoolExecutor的线程池是只有一个任务队列的,而ForkJoinPool有多个任务队列。通过ForkJoinPool的invoke或submit或execute提交任务的时候会根据一定规则分配给不同的任务队列,并且任务队列是双端队列
  • 使用ForkJoinPool能够使用数量有限的线程来完成非常多的具有父子关系的任务,比如使用4个线程来完成超过200万个任务。但是,使用ThreadPoolExecutor时,是不可能完成的,因为ThreadPoolExecutor中的Thread无法选择优先执行子任务,需要完成200万个具有父子关系的任务时,也需要200万个线程,显然这是不可行的。

工作窃取机制

  • 一般的线程池只有一个任务队列,但是对于Fork/Join框架来说,由于Fork出的各个子任务其实是平行关系,为了提高效率,减少线程竞争,将这些平行的任务放到不同的队列中去。
    由于线程处理不同任务的速度不同,这样就可能存在某个线程先执行完了自己队列中的任务的情况,这时为了提升效率,我们可以让该线程去“窃取”其它任务队列中的任务,这就是所谓的工作窃取算法。
    “工作窃取”的示意图如下,当线程1执行完自身任务队列中的任务后,尝试从线程2的任务队列中“窃取”任务:
    在这里插入图片描述
  • 工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且消耗了更多的系统资源,比如创建多个线程和多个双端队列。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值