我需要在读取流的时候对List进行同步吗?

10

如果我有一个线程敏感的列表,我通常会在迭代时采取以下方式:

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

我想知道如果我使用list.stream()并对流执行一些操作,比如筛选等,是否还需要将列表放入同步块中,还是流会复制列表?

谢谢

3个回答

9

流操作在内部使用spliterator()方法。

这里是ArrayList中的spliterator()方法:

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

它检查共修改,因此在您的情况下,stream() 操作必须在同步块内。

此外,在 Collections 中的 SynchronizedCollectionspliterator() 有注释。

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

这类似于iterator()中的注释:

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

这表明:在使用 stream() 操作时需要同步(至少,如果 iterator() 要求这样同步)。

最有说服力的是来自 SynchronizedCollectionstream() 方法:

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

3
使用流时,您需要像不使用流一样同步访问列表。同步应该由用户负责。
流本身并不能保证创建任何原始序列的副本。它可以在某些中间计算(例如 sort)期间创建一份副本,但您不应依赖此功能。因为流是不可重用的,所以每次使用流都这样做将浪费资源。
如果用户想要流在副本上操作,他们必须手动创建副本或者使用 CopyOnWriteArrayList 代替 ArrayList。
另外,请记住流是惰性的。底层序列直到执行终端操作(例如 collect、forEach)才被访问。

0

流可以被看作是原始数据的“视图”。这意味着:避免复制是它们本质的一部分。

只有像排序这样的操作需要创建输入的副本。因此:当您的输入可能会被另一个线程更改时,您需要保护它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接