Java fork/join framework小试兼RecursiveAction类的应用

假设要做这么一件事:给你一个double类型数组,让你求这个数组的元素的倒数和,怎么做?当然是先求倒数,然后再求和啦。没错,就是这样,但是如果你有多个cpu core呢?比如四个core, 线性地这么去求是不是有些浪费计算资源,或者说没有充分利用多核的条件以降低执行时间呢?那可不可以把这这个数组拆分成4段,每个核去计算一段的倒数和,然后等都计算完成了,再把结果相加呢?当然可以!就应该这样做,那java中怎么做呢?用fork/join框架就可以了,使用的方法之一是利用RecursiveAction类,我们只要自己新建一个任务类继承这个类,关键是要重写其compute()方法以实现其计算逻辑就可以了。下面是一个一般化地完成这个任务的例子:

 

package edu.coursera.parallel;
 
import java.util.concurrent.RecursiveAction;
 
/**
 * Class wrapping methods for implementing reciprocal array sum in parallel.
 */
public final class ReciprocalArraySum {
 
    /**
     * Default constructor.
     */
    private ReciprocalArraySum() {
    }
 
    private static int getNCores() {
        String ncoresStr = System.getenv("COURSERA_GRADER_NCORES");
        if (ncoresStr == null) {
            return Runtime.getRuntime().availableProcessors();
        } else {
            return Integer.parseInt(ncoresStr);
        }
    }
 
    /**
     * Sequentially compute the sum of the reciprocal values for a given array.
     *
     * @param input Input array
     * @return The sum of the reciprocals of the array input
     */
    protected static double seqArraySum(final double[] input) {
        double sum = 0;
 
        // Compute sum of reciprocals of array elements
        for (int i = 0; i < input.length; i++) {
            sum += 1 / input[i];
        }
 
        return sum;
    }
 
    /**
     * Computes the size of each chunk, given the number of chunks to create
     * across a given number of elements.
     *
     * @param nChunks The number of chunks to create
     * @param nElements The number of elements to chunk across
     * @return The default chunk size
     */
    private static int getChunkSize(final int nChunks, final int nElements) {
        // Integer ceil
        return (nElements + nChunks - 1) / nChunks;
    }
 
    /**
     * Computes the inclusive element index that the provided chunk starts at,
     * given there are a certain number of chunks.
     *
     * @param chunk The chunk to compute the start of
     * @param nChunks The number of chunks created
     * @param nElements The number of elements to chunk across
     * @return The inclusive index that this chunk starts at in the set of
     *         nElements
     */
    private static int getChunkStartInclusive(final int chunk,
            final int nChunks, final int nElements) {
        final int chunkSize = getChunkSize(nChunks, nElements);
        return chunk * chunkSize;
    }
 
    /**
     * Computes the exclusive element index that the provided chunk ends at,
     * given there are a certain number of chunks.
     *
     * @param chunk The chunk to compute the end of
     * @param nChunks The number of chunks created
     * @param nElements The number of elements to chunk across
     * @return The exclusive end index for this chunk
     */
    private static int getChunkEndExclusive(final int chunk, final int nChunks,
            final int nElements) {
        final int chunkSize = getChunkSize(nChunks, nElements);
        final int end = (chunk + 1) * chunkSize;
        if (end > nElements) {
            return nElements;
        } else {
            return end;
        }
    }
 
    /**
     * This class stub can be filled in to implement the body of each task
     * created to perform reciprocal array sum in parallel.
     */
    private static class ReciprocalArraySumTask extends RecursiveAction {
        /**
         * Starting index for traversal done by this task.
         */
        private final int startIndexInclusive;
        /**
         * Ending index for traversal done by this task.
         */
        private final int endIndexExclusive;
        /**
         * Input array to reciprocal sum.
         */
        private final double[] input;
        /**
         * Intermediate value produced by this task.
         */
        private double value;
 
        /**
         * Constructor.
         * @param setStartIndexInclusive Set the starting index to begin
         *        parallel traversal at.
         * @param setEndIndexExclusive Set ending index for parallel traversal.
         * @param setInput Input values
         */
        ReciprocalArraySumTask(final int setStartIndexInclusive,
                final int setEndIndexExclusive, final double[] setInput) {
            this.startIndexInclusive = setStartIndexInclusive;
            this.endIndexExclusive = setEndIndexExclusive;
            this.input = setInput;
        }
 
        /**
         * Getter for the value produced by this task.
         * @return Value produced by this task
         */
        public double getValue() {
            return value;
        }
 
        @Override
        protected void compute() {
            // TODO
            // calculate sequentially
            value = 0;
            for (int i = startIndexInclusive; i < endIndexExclusive; i++) {
                value += 1 / input[i];
            }
        }
    }
 
    /**
     * TODO: Modify this method to compute the same reciprocal sum as
     * seqArraySum, but use two tasks running in parallel under the Java Fork
     * Join framework. You may assume that the length of the input array is
     * evenly divisible by 2.
     *
     * @param input Input array
     * @return The sum of the reciprocals of the array input
     */
    protected static double parArraySum(final double[] input) {
        assert input.length % 2 == 0;
        int mid = input.length / 2;
        ReciprocalArraySumTask lower = new ReciprocalArraySumTask(0, mid, input);
        ReciprocalArraySumTask high = new ReciprocalArraySumTask(mid, input.length, input);
        lower.fork();
        high.compute();
        lower.join();
        return lower.getValue()+high.getValue();
    }
 
    /**
     * TODO: Extend the work you did to implement parArraySum to use a set
     * number of tasks to compute the reciprocal array sum. You may find the
     * above utilities getChunkStartInclusive and getChunkEndExclusive helpful
     * in computing the range of element indices that belong to each chunk.
     *
     * @param input Input array
     * @param numTasks The number of tasks to create
     * @return The sum of the reciprocals of the array input
     */
    protected static double parManyTaskArraySum(final double[] input,
            final int numTasks) {
        int taskNum = numTasks;
        if(taskNum>input.length){
            taskNum = input.length;
        }
        ReciprocalArraySumTask[] tasks = new ReciprocalArraySumTask[taskNum];
 
        int i = 0;
        for(i=0; i<taskNum-1; i++){
            tasks[i] = new ReciprocalArraySumTask(getChunkStartInclusive(i, taskNum, input.length), getChunkEndExclusive(i, taskNum, input.length), input);
            tasks[i].fork();
        }
        tasks[i] = new ReciprocalArraySumTask(getChunkStartInclusive(i, taskNum, input.length), getChunkEndExclusive(i, taskNum, input.length), input);
        tasks[i].compute();
 
        for(int j=0; j<taskNum-1; j++){
            tasks[j].join();
        }
 
        double sum = 0;
        for(int j=0; j<taskNum; j++){
            sum += tasks[j].getValue();
        }
        return sum;
    }
 
    public static void main(String[] args){
        double[] arr = {2,4,8,10};
 
        double seq = seqArraySum(arr);
        double half = parArraySum(arr);
        double many = parManyTaskArraySum(arr, 4);
 
        System.out.println(seq);
        System.out.println(half);
        System.out.println(many);
    }
}

  

posted on 2017-09-29 21:33  huangzifu  阅读(1055)  评论(0编辑  收藏  举报