当可以避免时,需要在迭代时手动同步同步列表吗?

问题描述 投票:7回答:3

我的问题是关于synchronizedList方法集合类。

Javadocs说:

It is imperative that the user manually synchronize on the returned list when 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());
}

虽然其他方法不需要手动同步。我查看了Collections类的源代码,发现shyncronization已经处理了像add这样的所有方法

public boolean add(E e) {
   synchronized(list) {return c.add(e);}
}

但不适用于迭代器方法。我认为迭代器方法也可以以与上述方法相同的方式处理同步(它可以避免额外的工作,即程序员的手动同步)。我相信它背后肯定有一些具体的原因,但我错过了它?

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

一种避免程序员手动同步的方法

public Iterator<E> iterator() {
   synchronized(list) {
       return c.iterator(); // No need to manually synched by user!
   }
}
java list collections synchronization java.util.concurrent
3个回答
17
投票

我认为迭代器方法也可以以与上述方法相同的方式处理同步

不,它绝对不可能。

迭代器无法控制代码在调用单个方法之间执行的操作。这才是重点。您的迭代代码将重复调用hasNext()next(),并且在这些调用期间的同步是可行但不相关的 - 重要的是没有其他代码尝试在您迭代的整个过程中修改列表。

所以想象一个时间表:

t = 0: call iterator()
t = 1: call hasNext()
t = 2: call next()
// Do lots of work with the returned item
t = 10: call hasNext()

迭代器不能在t = 2时调用next()的结束与t = 10时调用hasNext()之间同步。因此,如果另一个线程试图(比方说)在t = 7处将项添加到列表中,那么迭代器是如何阻止它这样做的呢?

这是同步集合的整体问题:每个单独的操作都是同步的,而通常您希望同步整个大块操作。


4
投票

如果不同步整个迭代,则另一个线程可以在迭代时修改集合,从而导致ConccurentModificationException。

此外,返回的迭代器不是线程安全的。 他们可以通过将迭代器包装在锁定迭代器中的每个方法的SynchronizedIterator中来解决这个问题,但这也无济于事 - 另一个线程仍然可以在两次迭代之间修改集合,并破坏所有内容。

这是Collections.synchronized*()方法完全无用的原因之一。 有关正确的线程安全集合使用的更多信息,请参阅my blog


2
投票

如果要避免手动同步,则必须使用java.util.concurrent.CopyOnWriteArrayList之类的集合。每次将对象添加到列表中时,都会复制基础数据结构以获得并发修改异常。

您在示例中需要对Iterator进行手动序列化的原因是Iterator使用与列表相同的内部数据结构,但它们是独立的对象,并且Iterator和list都可以在任意时刻由不同的线程访问。

另一个方法是制作列表的本地副本并迭代副本。

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.