Java 函数式编程 之并行Stream

我们已经在前面的几篇文章中已经在介绍stream api 的一些简单的使用,大都如下面的示例

    List list = Lists.newArrayList(1,2,3,4,5,6,7,8,9);
    list.stream().filter(x -> x % 2 ==0)
            .filter(x -> x > 8)
            .forEach(System.out::println);

同样的,我们也注意到另外一个函数,parallelStream, 看名字像是并行的Stream,将上面的代码改成parallelStream,

  list.stream().filter(x -> x % 2 ==0)
            .filter(x -> x > 8)
            .forEach(System.out::println);

这样就会用不同的线程分别处理每一个数据块的流了,多核处理器就开始忙起来了,实际上,并行流内部使用到了ForkJoinPool,那我们如何比较两者的性能呢,而且是不是我们加了parallel之后就是并行的了?出来的结果是不是一定正确的?
答案是否定的,因为在并行流中,处理的数据要被多个线程共享,那么就关系到了线程安全的问题,如果你加了同步锁,那么并行流就失去了意义,所有说并行流不是万能的,不仅仅如此,你还要考虑流背后的数据结构是不是便于拆解,比如说ArrayList和LinkedList, 因为ArrayList 是不需要进行遍历就可以被拆分进行操作,而LinkedList 却要进行遍历。

那么并行流内部用到的ForkJoinPool到底是哪方神圣,接下来我们来使用ForkJoinPool手写一个对于List中元素求和的功能。

public class ForkJoinPoolExample extends RecursiveTask {


private List list;
private int start;
private int end;
private final static int threshold = 4;

public ForkJoinPoolExample(List data) {
    this(data, 0, data.size());
}

public ForkJoinPoolExample(List data, int start, int end) {
    this.list = data;
    this.start = start;
    this.end = end;

}


@Override
protected Integer compute() {
    int len = end - start;
    if (len <= threshold) {
        return computerSequentially();
    }

    ForkJoinPoolExample left = new ForkJoinPoolExample(list, start, start + len / 2);
    left.fork();

    ForkJoinPoolExample right = new ForkJoinPoolExample(list, start + len / 2, end);
    right.fork();
    return left.join() + right.join();

}


public Integer computerSequentially() {
    int sum = 0;
    for (int i = start; i < end; i++) {
        sum = sum + list.get(i);
    }
    return sum;
}


public static void main(String[] arg) {

    ForkJoinPool forkJoinPool = new ForkJoinPool();

    List list = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6);

    ForkJoinPoolExample forkJoinPoolExample = new ForkJoinPoolExample(list);

    Integer sum = forkJoinPool.invoke(forkJoinPoolExample);

    System.out.println("The sum is " + sum);

}
}

关于ForkJoinPool,由于每一个核运行的效率不同,导致如果平均分配线程在核上运行的话,那样效率就低了,因此分支/合并 框架工程采用了工作窃取work stealing,采用了双向的链式队列,使得“有余力”可以在队列尾部获得新的任务运行.

那么高效的使用并行流有哪些需要注意的呢?

  1. 要注意,实现了并行流不一定比顺序流快,原因是,如果处理的对象存在互斥,那么相当于顺序流,关键还是要看基准测试。
  2. 注意拆箱和装箱的性能消耗,使用java8 中提供的原始类型流,IntStream,LongStrean,DoubleStream.
  3. 等等。

你可能感兴趣的:(Java 函数式编程 之并行Stream)