掌握系列之并发编程-10.Fork/Join框架

掌握高并发、高可用架构

第二课 并发编程

从本课开始学习并发编程的内容。主要介绍并发编程的基础知识、锁、内存模型、线程池、各种并发容器的使用。

第十节 Fork/Join框架

Fork/Join 分而治之 ForkJoinPool

基本思想

ThreadPoolExecutor线程池中每个任务都由单个线程独立处理。如果出现一个非常耗时的任务,就会出现线程池中只有一个线程在处理这个大任务,而其他线程却空闲着。这会导致CPU负载不均衡。

ForkJoinPool,将一个大任务拆分成多个小任务,使用fork将小任务分发给其他线程同时处理,然后使用join将小任务的执行结果汇总。它利用多处理器的优势,集中所有可用的处理能力来增强执行效率,这就是分而治之思想的并行实现。

基本原理

ForkJoinPool也是ExecutorService接口的实现类。

线ç¨æ± ç

ForkJoinPool的两大核心是分而治之工作窃取(Work Stealing)算法。

工作窃取算法

算法思想:

  1. 每个线程都有自己的WorkQueue,它是一个双端队列
  2. 队列支持三个功能,push、pop、poll
  3. push/pop只能被队列的所有者线程使用,而poll能被其他线程调用
  4. 划分的子任务调用fork时,都会被push到线程的队列中
  5. 默认情况下,线程从自己的队列中获取任务并执行
  6. 当自己的队列为空时,随机从另外的线程的队列末尾调用poll窃取任务
创建ForkJoinPool对象
  1. 调用Executors工具类
// parallelism定义并行级别
public static ExecutorService newWorkStealingPool(int parallelism);
// Runtime.getRuntime().availableProcessors()为并行级别
public static ExecutorService newWorkStealingPool();
  1. 调用ForkJoinPool内部方法commonPool()
public static ForkJoinPool commonPool();
  1. 调用构造器
ForkJoinTask

大多数时候,我们都是提交ForkJoinTaskFormJoinPool

以下是ForkJoinTask的三个核心方法:

  • fork(),大任务划分为小任务后,调用小任务的fork()方法可以将任务放入线程池中
  • join(),调用小任务的join()方法等待任务的返回结果。如果子任务抛出异常,join也会抛出异常;有方法quietlyJoin()不会抛出异常也不会返回结果,需要调用getException()getResult()
  • invoke(),在当前线程中同步执行该任务
RecursiveActionRecursiveTask

通常我们不会直接使用ForkJoinTask,而是使用它的两个抽象类:

  • RecursiveAction:没有返回值的任务
  • RecursiveTask:有返回值的任务

ForkJoinTask

public class RecursiveActionTeset {

    static class Sorter extends RecursiveAction {

        public static void sort(long[] array) {
            ForkJoinPool.commonPool().invoke(new Sorter(array, 0, array.length));
        }

        private final long[] array;
        private final int lo, hi;

        public Sorter(long[] array, int lo, int hi) {
            this.array = array;
            this.lo = lo;
            this.hi = hi;
        }

        private static final int THRESHOLD = 1000;

        // 大任务拆分的方法
        @Override
        protected void compute() {
            if (hi - lo < 1000) {
                Arrays.sort(array, lo, hi);
            } else {
                int mid = (hi + lo) >>> 1;
                // 长度大于1000时,平均分成两个数组
                Sorter left = new Sorter(array, lo, mid);
                Sorter right = new Sorter(array, mid, hi);
                invokeAll(left, right);

                merge(lo, mid, hi);
            }
        }

        private void merge(int lo, int mid, int hi) {
            long[] buff = Arrays.copyOfRange(array, lo, mid);
            for (int i = 0, j = lo, k = mid; i < buff.length; i++) {
                if (k == hi || buff[i] < array[k]) {
                    array[j] = buff[i++];
                } else {
                    array[j] = array[k++];
                }
            }
        }

        public static void main(String[] args) {
            long[] array = new Random().longs(100_0000).toArray();
            Sorter.sort(array);
            System.out.println(Arrays.toString(array));
        }
    }
}
public class BatchInsertTask extends RecursiveTask<Integer> {
    //要插入的数据
    List<Integer> records;

    public BatchInsertTask(List<Integer> records) {
        this.records = records;
    }

    @Override
    protected Integer compute() {
        //当要插入的数据少于5,则直接插入
        if (records.size() < 5) {
            return computeDirectly();
        } else {
            //如果要插入的数据大于等于5,则进行分组插入
            int size = records.size();

            //第一个分组
            BatchInsertTask aTask = new BatchInsertTask(records.subList(0, size / 2));
            //第二个分组
            BatchInsertTask bTask = new BatchInsertTask(records.subList(size / 2, records.size()));
            //两个任务并发执行起来
            invokeAll(aTask, bTask);
            //两个分组的插入的行数加起来
            return aTask.join() + bTask.join();
        }
    }

    /**
     * 真正插入数据的逻辑
     */
    private int computeDirectly() {
        try {
            Thread.sleep((long) (records.size() * 1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("插入了:" + Arrays.toString(records.toArray()));
        return records.size();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值