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

时间:2013-09-18 13:27:43

标签: java list collections synchronization java.util.concurrent

我的问题是关于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已经处理了所有方法,如添加

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!
   }
}

3 个答案:

答案 0 :(得分: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时将一个项添加到列表中,那么迭代器是如何阻止它这样做的呢?

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

答案 1 :(得分:4)

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

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

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

答案 2 :(得分:2)

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

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

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