152

我正在使用 a CollectionHashMap由 JPA 间接使用,它确实如此),但显然代码随机抛出一个ConcurrentModificationException. 是什么原因造成的,我该如何解决这个问题?通过使用一些同步,也许?

这是完整的堆栈跟踪:

Exception in thread "pool-1-thread-1" java.util.ConcurrentModificationException
        at java.util.HashMap$HashIterator.nextEntry(Unknown Source)
        at java.util.HashMap$ValueIterator.next(Unknown Source)
        at org.hibernate.collection.AbstractPersistentCollection$IteratorProxy.next(AbstractPersistentCollection.java:555)
        at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:296)
        at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:242)
        at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:219)
        at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
        at org.hibernate.engine.Cascade.cascade(Cascade.java:130)
4

8 回答 8

292

这不是同步问题。如果被迭代的底层集合被迭代器本身以外的任何东西修改,就会发生这种情况。

Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
    Entry item = it.next();
    map.remove(item.getKey());
}

这将在第二次调用ConcurrentModificationException时抛出一个。it.hasNext()

正确的方法是

Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
    Entry item = it.next();
    it.remove();
}

假设此迭代器支持该remove()操作。

于 2009-03-02T15:04:45.027 回答
84

尝试使用 aConcurrentHashMap而不是普通的HashMap

于 2009-03-02T16:40:37.137 回答
25

大多数类都不允许修改使用 anCollection迭代的 while 。Java 库将在迭代时尝试修改它称为“并发修改”。不幸的是,这表明唯一可能的原因是多个线程同时修改,但事实并非如此。仅使用一个线程就可以为(使用,或增强循环)创建迭代器,开始迭代(使用,或等效地进入增强循环的主体),修改,然后继续迭代。CollectionIteratorCollectionCollectionCollectionCollection.iterator()forIterator.next()forCollection

为了帮助程序员,这些类的一些实现尝试检测错误的并发修改,如果检测到则抛出一个错误。然而,保证检测到所有并发修改通常是不可能和实际的。因此,错误使用并不总是导致抛出.CollectionConcurrentModificationExceptionCollectionConcurrentModificationException

的文档ConcurrentModificationException说:

当这种修改是不允许的时,检测到对象的并发修改的方法可能会抛出此异常...

请注意,此异常并不总是表示对象已被不同的线程同时修改。如果单个线程发出一系列违反对象约定的方法调用,则该对象可能会抛出此异常...

请注意,不能保证快速失败的行为,因为一般来说,在存在不同步的并发修改的情况下,不可能做出任何硬保证。快速失败的操作ConcurrentModificationException是在尽力而为的基础上进行的。

注意

HashSetHashMapTreeSet类的文档是这样ArrayList说的:

[从此类直接或间接] 返回的迭代器是快速失败的:如果在创建迭代器后的任何时间修改 [集合],除了通过迭代器自己的 remove 方法之外的任何方式,Iterator抛出一个ConcurrentModificationException. 因此,面对并发修改,迭代器快速而干净地失败,而不是在未来不确定的时间冒任意的、非确定性的行为。

请注意,不能保证迭代器的快速失败行为,因为一般来说,在存在不同步的并发修改的情况下,不可能做出任何硬保证。快速失败的迭代器ConcurrentModificationException在尽力而为的基础上抛出。因此,编写一个依赖于这个异常的正确性的程序是错误的:迭代器的快速失败行为应该只用于检测错误

再次注意,行为“无法得到保证”并且只是“在尽力而为的基础上”。

接口的几种方法的文档是Map这样说的:

非并发实现应覆盖此方法,并尽最大努力ConcurrentModificationException在计算期间检测到映射函数修改此映射时抛出 a。并发实现应该覆盖这个方法,并且在最大努力的基础上,IllegalStateException如果检测到映射函数在计算期间修改了这个映射并且因此计算永远不会完成,则抛出一个。

再次注意,检测只需要“尽力而为基础”,并且ConcurrentModificationException仅针对非并发(非线程安全)类明确建议使用 a。

调试ConcurrentModificationException

因此,当您看到由于 a 导致的堆栈跟踪时ConcurrentModificationException,您不能立即假设原因是对 a 的不安全多线程访问Collection。您必须检查堆栈跟踪以确定哪个类Collection引发了异常(该类的方法将直接或间接引发它)以及哪个Collection对象。然后您必须检查可以从何处修改该对象。

  • 最常见的原因是Collection在. 仅仅因为您在源代码中没有看到对象并不意味着不存在!幸运的是,错误循环的其中一条语句通常会在堆栈跟踪中,因此跟踪错误通常很容易。forCollectionIteratorIteratorfor
  • 更棘手的情况是您的代码传递对Collection对象的引用。请注意,集合的不可修改视图(例如由 生成Collections.unmodifiableList())保留对可修改集合的引用,因此对“不可修改”集合的迭代可能会引发异常(修改已在其他地方完成)。您的其他视图Collection,例如子列表Map条目集Map键集也保留对原始(可修改)的引用Collection。即使对于线程安全的 ; 这也可能是一个问题Collection,例如CopyOnWriteList; 不要假设线程安全(并发)集合永远不会抛出异常。
  • 在某些情况下,哪些操作可以修改 aCollection可能是意料之外的。例如,LinkedHashMap.get()修改其集合
  • 最困难的情况是异常由于多个线程的并发修改引起的。

防止并发修改错误的编程

如果可能,限制对Collection对象的所有引用,这样更容易防止并发修改。制作Collection一个private对象或一个局部变量,并且不要Collection从方法返回对 the 或其迭代器的引用。然后检查所有Collection可以修改的地方要容易得多。如果Collection要由多个线程使用,那么确保线程Collection仅通过适当的同步和锁定访问 是切实可行的。

于 2019-03-13T11:57:02.633 回答
10

在 Java 8 中,您可以使用 lambda 表达式:

map.keySet().removeIf(key -> key condition);
于 2019-11-20T07:08:47.227 回答
2

这听起来不像是 Java 同步问题,而更像是数据库锁定问题。

我不知道向所有持久类添加一个版本是否会解决它,但这是 Hibernate 可以提供对表中行的独占访问的一种方式。

可能是隔离级别需要更高。如果您允许“脏读”,也许您需要升级到可序列化。

于 2009-03-02T15:04:28.317 回答
0

根据您要执行的操作,尝试 CopyOnWriteArrayList 或 CopyOnWriteArraySet。

于 2009-03-02T16:04:01.967 回答
0

请注意,如果您像我一样在迭代地图时尝试从地图中删除一些条目,则所选答案不能在某些修改之前直接应用于您的上下文。

我只是在这里为新手提供我的工作示例以节省他们的时间:

HashMap<Character,Integer> map=new HashMap();
//adding some entries to the map
...
int threshold;
//initialize the threshold
...
Iterator it=map.entrySet().iterator();
while(it.hasNext()){
    Map.Entry<Character,Integer> item=(Map.Entry<Character,Integer>)it.next();
    //it.remove() will delete the item from the map
    if((Integer)item.getValue()<threshold){
        it.remove();
    }
于 2018-07-06T08:02:19.433 回答
0

我在尝试从列表中删除最后 x 个项目时遇到了这个异常。 myList.subList(lastIndex, myList.size()).clear();是唯一对我有用的解决方案。

于 2020-06-15T14:03:30.087 回答