iterator.hasNext()
或iterator.next()
的任何调用都会抛出ConcurrentModificationException
异常。即使是同步的集合包装类SynchronizedMap
和SynchronizedList
也只是有条件的线程安全,这意味着所有单个操作都是线程安全的,但是复合操作(其中控制流取决于先前操作的结果)可能会受到线程问题的影响。问题是:如何在不影响性能的情况下避免此问题。注意:我知道CopyOnWriteArrayList
。iterator.hasNext()
或iterator.next()
的任何调用都会抛出ConcurrentModificationException
异常。即使是同步的集合包装类SynchronizedMap
和SynchronizedList
也只是有条件的线程安全,这意味着所有单个操作都是线程安全的,但是复合操作(其中控制流取决于先前操作的结果)可能会受到线程问题的影响。问题是:如何在不影响性能的情况下避免此问题。注意:我知道CopyOnWriteArrayList
。CopyOnWriteArrayList
或ConcurrentHashMap
,或者您可以使用与CAS一起工作的Atomic*
类。如果您不知道Atomic*
类,它们绝对值得一看!您可以查看此问题。synchronized
装饰器类会锁定整个Map
。而另一方面,ConcurrentHashMap
只会在你修改/删除时锁定一个桶。 - Adam Aroldsynchronized(list) {
Iterator i = list.iterator(); // Must be in synchronized block
while (i.hasNext())
foo(i.next());
}
或者使用
Collections.synchronizedList(arrayList);
但是两者都无法提供完整的线程安全功能。
通过使用这个包,对集合的所有访问都是原子性的,并且一些类提供了在构造迭代器时列表状态的快照(参见CopyOnWriteArrayList
)。CopyOnWriteArrayList
在读取时很快,但如果您执行多次写入,则可能会影响性能。
因此,如果不需要 CopyOnWriteArrayList
,可以看看 ConcurrentLinkedQueue
,它提供了一个“弱一致性”迭代器,永远不会抛出 ConcurrentModificationException,并保证遍历元素时按照迭代器构造时的顺序进行。除非您需要更频繁地访问特定索引处的元素而不是遍历整个集合,否则这个方法在所有方面都很高效。
另一个选项是使用ConcurrentSkipListSet
,它提供了期望的平均log(n)时间成本,用于包含、添加和删除操作及其变体。插入、删除和访问操作可以安全地由多个线程并发执行
,而且迭代器是弱一致性的
。
哪些并发(线程安全)集合取决于您最常执行的操作类型。由于它们都是Java Collection框架的一部分,因此您可以根据需要随时进行交换。
CopyOnWriteArrayList
的提示。答案已经修正。 - Yanick Rochon我认为你提出了一个有趣的问题。
我尝试思考一下,例如像其他人建议的ConcurrentHashMap是否可以帮助解决问题,但我不确定因为锁是基于段的。
在这种情况下,我会锁定对您的集合的访问,使用ReaderWriterLock。
我选择这个锁的原因是因为我觉得这需要锁定(就像你所解释的——迭代由多个操作组成),
而且因为在读取线程的情况下,如果没有写入线程在处理集合,则不希望它们等待锁定。
感谢@Adam Arold,我注意到你建议的“同步装饰器”——但是我觉得这个装饰器对你的需求来说“太强大”了,因为它使用了同步,并且不会区分N个读者和M个写作者的情况。
如果您有一个未封装的非线程安全类的Collection
对象,那么无法防止Collection
的误用,因此可能会出现ConcurrentModificationException
。
其他答案建议使用线程安全的Collection
类,例如由java.util.concurrent
提供的类。但是,您应该考虑封装Collection
对象:将对象设置为private
,并且使您的类提供更高级别的抽象(例如addPerson
和removePerson
),代表调用者操作Collection
,并且没有任何getter方法返回对Collection
的引用。然后,可以相当容易地对封装数据执行不变量(例如“每个人都有一个非空名称”)进行强制执行,并使用synchronized
提供线程安全性。