假设要做这么一件事:给你一个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);
}
}