5、并行流与并行排序

Java 8中,可以在接口不变的情况下,将流改为并行流。这样,就可以很自然地使用多线程进行集合中的数据处理。

5.1、使用并行流过滤数据

现在让我们考虑这么一个简单的案例,我们希望可以统计1~1000000内所有的质数的数量。首先,我们需要一个判断质数的函数:

public class PrimeUtil {

    public static boolean isPrime(int number){
        int temp = number;
        if(temp<2){
            return false;
        }
        for(int i = 2;Math.sqrt(temp)>=i;i++){
            System.out.println(Math.sqrt(temp));
            if(temp % i==0){
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
       IntStream.range(1,15).filter(PrimeUtil::isPrime).forEach(System.out::println);
    }
}

上述函数给定一个数字,如果这个数字是质数就返回true,否则返回false。 接着,使用函数式编程统计给定范围内所有的质数:

IntStream.range(1, 1000000).filter(PrimeUtil::isPrime).count();

上述代码首先生成一个1到1000000的数字流。接着使用过滤函数,只选择所有的质数,最后进行数量统计。
上述代码是串行的,将它改造成并行计算非常简单,只需要将流并行化即可:

IntStream.range(1, 1000000).parallel().filter(PrimeUtil::isPrime).count();

上述代码中,首先parallel()方法得到一个并行流,接着,在并行流上进行过滤,此时,PrimeUtil.isPrime()函数会被多线程并发调用,应用于流中的所有元素。

5.2、从集合到并行流

在函数式编程中,我们可以从集合得到一个流或者并行流。下面这段代码试图统计集合内所有学生的平均分:

List<Student> ss=new ArrayList<Student>();
double ave=ss.stream().mapToInt(s->s.score).average().getAsDouble();

从集合对象List中,我们使用stream()方法可以得到一个流。如果希望将这段代码并行化,则可以使用parallelStream()函数。

double ave=ss.parallelStream().mapToInt(s->s.score).average().getAsDouble();

可以看到,将原有的串行方式改造成并行执行是非常容易的。

5.3 并行排序

除了并行流外,对于普通数组,Java 8中也提供了简单的并行功能。比如,对于数组排序,我们有Arrays.sort()方法。当然这是串行排序,但在Java 8中,我们可以使用新增的Arrays. parallelSort()方法直接使用并行排序。 比如,你可以这样使用:

int[] arr=new int[10000000];
Random random = new Random();
Arrays.setAll(arr,x->random.nextInt());
Arrays.stream(arr).forEach(System.out::println);

除了并行排序外,Arrays中还增加了一些API用于数组中数据的赋值,比如:

public static void setAll(int[] array, IntUnaryOperator generator)

这是一个函数式味道很浓的接口,它的第2个参数是一个函数式接口。如果我们想给数组中每一个元素都附上一个随机值,则可以这么做:

Random r=new Random(); 
Arrays.setAll(arr, (i)->r.nextInt());

当然,以上过程是串行的。但是只要使用setAll()对应的并行版本,你就可以很快将它执行在多个CPU上:

Random r=new Random();
Arrays.parallelSetAll (arr, (i)->r.nextInt());