Anna Klein
Anna Klein

Reputation: 2171

Do I have to synchronize on a List that is read by a stream?

if I have a Thread sensitive List I go usually like this while iterating over it:

    List list = Collections.synchronizedList(new ArrayList());
...
synchronized(list) {
    Iterator i = list.iterator(); // Must be in synchronized block
    while (i.hasNext())
        foo(i.next());   
}

I wonder if I use list.stream() and do then some operations over the stream like filter etc., if I also have to put the list into a synchronize block or does the stream makes a copy of the list?

Thanks

Upvotes: 10

Views: 5813

Answers (3)

Sergey Khudyakov
Sergey Khudyakov

Reputation: 1182

Yes, you have to synchronize access to the list when using streams in the same way you would do without streams. Synchronization should be taken care of by a user.

A stream by itself doesn't guarantee to create any copy of the original sequence. It can make a copy during some intermediate computations (e.g. sort), but you should not rely on that. And doing this for each usage of a stream would be a waste of resources since streams are not reusable.

If a user wants a stream to operate over a copy, they have to manually create a copy or use CopyOnWriteArrayList instead of ArrayList, for example.

Moreover, keep in mind that streams are lazy. The underlying sequence is not accessed until a terminal operation (e.g. collect, forEach) is executed.

Upvotes: 3

GhostCat
GhostCat

Reputation: 140457

A stream can be seen as a "view" on the original data. This means: it is part of their nature to avoid making copies.

Only operations like sorting require creating a copy of the input. Thus: when your input can be changed by another thread - then you need to protect against that.

Upvotes: 0

Roman Puchkovskiy
Roman Puchkovskiy

Reputation: 11835

Stream operations use spliterator() method internally.

Here is the spliterator() method from ArrayList:

    public Spliterator<E> spliterator() {
        checkForComodification();
        return new ArrayListSpliterator<E>(ArrayList.this, offset,
                                           offset + this.size, this.modCount);
    }

It checks for comodification, so it looks like stream() operations have to be inside synchronized blocks in your case.

Also, spliterator() of SynchronizedCollection (in Collections) has comment

    public Spliterator<E> spliterator() {
        return c.spliterator(); // Must be manually synched by user!
    }

which is analogous to the comment in iterator():

    public Iterator<E> iterator() {
        return c.iterator(); // Must be manually synched by user!
    }

which shows the same: synchronization is needed around stream() operations (at least, if iterator() requires such a synchronization).

And the most convincing: stream() method from SynchronizedCollection:

    public Stream<E> stream() {
        return c.stream(); // Must be manually synched by user!
    }

Upvotes: 9

Related Questions