每当我们使用 java.util Collection 类时,如果一个线程更改了一个集合,而另一个线程正在使用迭代器遍历它,那么任何对iterator.hasNext()
或的调用iterator.next()
都会抛出ConcurrentModificationException
。即使是synchronized
集合包装类SynchronizedMap
,SynchronizedList
也只是有条件的线程安全,这意味着所有单独的操作都是线程安全的,但是控制流取决于先前操作结果的复合操作可能会受到线程问题的影响。问题是:如何在不影响性能的情况下避免这个问题。注意:我知道CopyOnWriteArrayList
.
4 回答
这是因为“标准”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
(请参阅CopyOnWriteArrayList
。CopyOnWriteArrayList
读取速度很快,但如果您执行许多写入操作,这可能会影响性能。
因此,如果CopyOnWriteArrayList
不需要,请查看ConcurrentLinkedQueue
哪些优惠a "weakly consistent" iterator that will never throw ConcurrentModificationException, and guarantees to traverse elements as they existed upon construction of the iterator
。这在所有方面都很有效,除非您必须更频繁地访问特定索引处的元素而不是遍历整个集合。
另一种选择是ConcurrentSkipListSet
whichprovides 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 threads
和iterators are weakly consistent
as well。
哪些并发(线程安全)集合取决于您执行最多的操作类型。由于它们都是 Java Collection 框架的一部分,因此您可以将它们交换到您需要的任何位置。
我认为你提出了一个有趣的问题。
例如,我尝试考虑其他人建议的 ConcurrentHashMap 是否可以提供帮助,但我不确定锁是基于段的。
在这种情况下,我会做的,我希望我能很好地理解你的问题,就是使用ReaderWriterLock 锁定对你的集合的访问。
我选择这个锁的原因是因为我确实觉得这需要锁定(正如你所解释的 - 迭代由几个操作组成),
而且因为在读取线程的情况下,如果没有写入线程正在处理集合,我不希望他们等待 lock 。感谢@Adam Arold,我注意到您建议使用“同步装饰器” - 但我觉得这个装饰器对于您的需求来说“太强大了”,因为它使用同步并且不会区分 N 读者和 M 作家的情况。
如果您有一个非线程安全类的未封装对象Collection
,则无法防止误用Collection
,从而防止.ConcurrentModificationException
其他答案建议使用线程安全Collection
类,例如java.util.concurrent
. 但是,您应该考虑封装Collection
对象:让对象 be ,并让您的类提供代表调用者操纵 的private
更高级别的抽象(例如addPerson
和) ,并且没有任何返回对 . 的引用的 getter 方法。然后在封装的数据上强制执行不变量(例如“每个人都有一个非空名称”)并使用.removePerson
Collection
Collection
synchronized