adding parallell to a stream causes NullPointerException

时间:2017-08-19 12:45:33

标签: parallel-processing java-stream

I'm trying to get my head around Java streams. It was my understanding that they provide an easy way to parallellize behaviour, and that also not all operations benefit from parallellization, but that you always have the option to do it by just slapping .parallell() on to an existing stream. This might make the stream go slower in some cases, or return the elements in a different order at the end etc, but you always have the option to parallellize a stream. That's why I got confused when I changed this method:

public static List<Integer> primeSequence() {
    List<Integer> list = new LinkedList<Integer>();
    IntStream.range(1, 10)
            .filter(x -> isPrime(x))
            .forEach(list::add);
    return list;
}
//returns {2,3,5,7}

to this:

public static List<Integer> primeSequence() {
    List<Integer> list = new LinkedList<Integer>();
    IntStream.range(1, 10).parallel()
            .filter(x -> isPrime(x))
            .forEach(list::add);
    return list;
}
//throws NullPointerException();

I thought all streams were serial unless otherwise stated and parallel() just made then execute in parallel. What am I missing here? Why does it throw an Exception?

1 个答案:

答案 0 :(得分:1)

您的初始primeSequence方法实现存在一个重要问题 - 您将流迭代与外部列表修改混合在一起。你应该避免使用这种方式的流,否则你将面临很多问题。就像你描述的那个。如果您看一下add(E element)方法的实现方式,您会看到如下内容:

public boolean add(E e) {
    this.linkLast(e);
    return true;
}

void linkLast(E e) {
    LinkedList.Node<E> l = this.last;
    LinkedList.Node<E> newNode = new LinkedList.Node(l, e, (LinkedList.Node)null);
    this.last = newNode;
    if (l == null) {
        this.first = newNode;
    } else {
        l.next = newNode;
    }

    ++this.size;
    ++this.modCount;
}

如果在示例中使用CopyOnWriteArrayList而不是LinkedList,则不会抛出NullPointerException - 仅因为CopyOnWriteArrayList使用锁定进行多线程执行同步:

public boolean add(E e) {
    ReentrantLock lock = this.lock;
    lock.lock();

    boolean var6;
    try {
        Object[] elements = this.getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        this.setArray(newElements);
        var6 = true;
    } finally {
        lock.unlock();
    }

    return var6;
}

但它仍然不是利用并行流的最佳方式。

使用Stream API的正确方法

请考虑对您的代码进行以下修改:

public static List<Integer> primeSequence() {
    return IntStream.range(1, 10)
        .parallel()
        .filter(x -> isPrime(x))
        .boxed()
        .collect(Collectors.toList());
}

我们正在收集结果并返回最终列表,而不是修改某些外部列表(任何类型)。您可以使用.stream()方法将任何列表转换为流,并且您不必担心初始列表 - 您将应用于该列表的所有操作都不会修改输入,结果将是输入列表的副本。

我希望它有所帮助。