如何在多线程代码中避免ConcurrentModificationException

时间:2012-11-10 14:14:06

标签: java collections concurrency

每当我们使用java.util Collection类时,如果一个线程更改了一个集合,而另一个线程使用迭代器遍历它,那么任何对iterator.hasNext()iterator.next()的调用都会抛出{ {1}}。即使ConcurrentModificationException集合包装类synchronizedSynchronizedMap也只是有条件地线程安全的,这意味着所有单独的操作都是线程安全的,但复合操作的控制流程取决于之前的结果操作可能受到线程问题的影响。问题是:如何在不影响性能的情况下避免此问题。注意:我知道SynchronizedList

4 个答案:

答案 0 :(得分:2)

如上所述,您可以使用CopyOnWriteArrayListConcurrentHashMap等,也可以使用与CAS合作的Atomic*类。

如果您不了解Atomic*课程,他们绝对值得一看!您可以查看this问题。

因此,要回答您的问题,您必须为该任务选择合适的工具。由于您不与我们分享上下文,我可以猜测。在某些情况下,CAS将在其他并发集合中表现更好。

如果不清楚,您可以随时查看官方Oracle Trails:Lesson: Concurrency

答案 1 :(得分:1)

我认为你提出了一个有趣的问题。
我试着想一下ConcurrentHashMap是否像其他人建议的那样可以提供帮助,但我不确定锁是否是基于段的。
在这种情况下我会做什么,我希望我能很好地理解你的问题,就是使用ReaderWriterLock.来锁定对你的收藏品的访问权限。 我选择这个锁的原因是因为我觉得这需要锁定(正如你所解释的那样 - 迭代是由几个操作组成的),
因为在读取器线程的情况下,如果没有编写器线程正在处理集合,我不希望它们等待锁定。 感谢@Adam Arold我注意到你建议使用“synchronized decorator” - 但是我觉得这个装饰器“太强大”了你的需求,因为它使用了一个同步的,并且不会在N个读者和M个作者的情况之间产生差异。

答案 2 :(得分:0)

这是因为“标准”Java集合不是线程安全的,因为它们不同步。使用多个线程访问您的集合时,您应该查看java.util.concurrent包。

如果没有这个软件包,在Java 5之前,必须执行手动同步:

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

或使用

Collections.synchronizedList(arrayList);

但两者都无法真正提供完整的线程安全功能。

使用这个包,所有对集合的访问都是以原子方式进行的,并且有些类provide a snapshot of the state of the list when the iterator was constructed(请参阅CopyOnWriteArrayListCopyOnWriteArrayList快速读取,但如果您正在执行多次写入,这可能会影响绩效。

因此,如果不需要CopyOnWriteArrayList,请查看提供a "weakly consistent" iterator that will never throw ConcurrentModificationException, and guarantees to traverse elements as they existed upon construction of the iterator的{​​{3}}。这一点在所有方面都是有效的,除非您必须更频繁地访问特定索引处的元素,而不是遍历整个集合。

另一个选项是ConcurrentLinkedQueue provides expected average log(n) time cost for the contains, add, and remove operations and their variants. Insertion, removal, and access operations safely execute concurrently by multiple threadsiterators are weakly consistent

哪些并发(线程安全)集合取决于您执行的操作类型最多。由于它们都是Java Collection框架的一部分,因此您可以将它们交换到您需要的地方。

答案 3 :(得分:0)

如果您具有非线程安全类的未封装的Collection对象,则无法防止滥用Collection以及the possibility of a ConcurrentModificationException

其他答案建议使用线程安全的Collection类,例如java.util.concurrent提供的类。但是,您应该consider encapsulating the Collection object:将对象设为private,并让您的类提供可操作addPerson的更高级别的抽象(例如removePersonCollection)代表调用者,并且没有任何getter方法返回对Collection的引用。这样就很容易对封装的数据强制执行不变式(例如“每个人的名字都为非空”),并且使用synchronized提供线程安全性。