重做一道Java面試題(Fork/Join)

since1986發表於2017-09-17

前幾天參加了一場面試,當時有這莫一道題:

如何充分利用多核CPU,計算很大List中所有整數的和?
複製程式碼

老實說,我當時並沒有想出來具體該如何實現,只是有個大致的方向,肯定是分治法的思想;這兩天我一直在嘗試將這些當時沒做出來的題想辦法做出來,查了一些資料,看了若干文章,現在反過頭來再來嘗試解決一下這個題吧。

經過這兩天的學習,我基本上搜集到了兩種解這道題的思路: 1.用CyclicBarrier 這種方法,有網友給出了詳盡的解釋,在此不再複述。

2.用Fork/Join 這個方法我是受到了這幾篇文章的啟發:

具體的看程式碼吧:

package com.github.since1986.test;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
import java.util.stream.IntStream;

/**
 * Created by since1986 on 2017/9/17.
 */
public class ForkJoinTest {

    public static void main(String... args) throws InterruptedException, ExecutionException {
        int[] array = IntStream.rangeClosed(0, 1_00_000_000).toArray(); //模擬一個“很大”的List,這裡用直接用陣列代替了(題目裡其實也沒說明白“很大”到底是什麼概念,實際上太大了會OOM,但我覺得這道題主要考查的是對併發程式設計的基本思路吧,應該不會考察太深的,所以不用在意了,要是確實是要把怎麼樣避免OOM也考慮到的話暫時還沒想到應該怎樣解決)

        //簡單粗暴的做法
        int sum = 0;
        for (int i = 0; i < array.length; i++) {
            sum += array[i];
        }
        System.out.println(sum);

        //Fork/Join的做法
        ForkJoinPool forkJoinPool = new ForkJoinPool(); //起一個數量等於可用CPU核數的池子(對應題目中“充分利用多核”)
        Task task = new Task(0, array.length, 10_000, array);

        Future<Integer> future = forkJoinPool.submit(task); //提交Task
        System.out.println(future.get()); //獲得返回值

        forkJoinPool.shutdown(); //關閉池子
    }

    static class Task extends RecursiveTask<Integer> {

        public static final int DEFAULT_THRESHOLD = 1000;
        private int high, low;
        private int threshold;
        private int[] array;

        Task(int low, int high, int threshold, int[] array) {
            this.array = array;
            this.low = low;
            this.high = high;
            this.threshold = threshold; //任務劃分的最小值(若為1000則含義是Fork到1000大小時就不再繼續Fork了)
        }

        @Override
        protected Integer compute() {
            //System.out.println("low: " + low + "  high: " + high);
            if (high - low <= threshold) { //到了不能再Fork的閾值後直接迴圈累加返回
                int sum = 0;
                for (int i = low; i < high; i++) {
                    sum += array[i];
                }
                //System.out.println("sum: " + sum);
                return sum;
            } else { //沒有到閾值的話,繼續遞迴拆分任務為左任務和右任務(分治法的思想)
                int middle = (high - low) / 2 + low;
                //System.out.println("middle: " + middle);
                Task leftHandTask = new Task(low, middle, threshold, array); //左任務
                Task rightHandTask = new Task(middle, high, threshold, array); //右任務
                leftHandTask.fork(); //左任務還要繼續拆,直到滿足上邊if裡的閾值條件
                rightHandTask.fork(); //右任務也要繼續拆,直到滿足上邊if裡的閾值條件
                return leftHandTask.join() + rightHandTask.join(); //最後Join得到結果
            }
        }
    }
}
複製程式碼

相關文章