掌握系列之併發編程-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();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章