Java 为什么会抛出ConcurrentModificationException以及如何调试它

Java 为什么会抛出ConcurrentModificationException以及如何调试它,java,exception,collections,concurrentmodification,Java,Exception,Collections,Concurrentmodification,我使用的是集合(JPA间接使用的HashMap,但很明显,代码会随机抛出一个ConcurrentModificationException。是什么原因导致此问题?如何解决此问题?也许是通过使用一些同步 以下是完整的堆栈跟踪: Exception in thread "pool-1-thread-1" java.util.ConcurrentModificationException at java.util.HashMap$HashIterator.nextEntry(Unkno

我使用的是
集合
(JPA间接使用的
HashMap
,但很明显,代码会随机抛出一个
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)

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

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


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

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

Iterator it = map.entrySet().iterator();
while (it.hasNext())
{
   Entry item = it.next();
   map.remove(item.getKey());
}
当第二次调用
it.hasNext()
时,这将抛出一个
ConcurrentModificationException

正确的方法是

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

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

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

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

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

为了节省新手的时间,我在这里给出了我的工作示例:

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();
    }
HashMap map=newhashmap();
//向地图添加一些条目
...
int阈值;
//初始化阈值
...
迭代器it=map.entrySet().Iterator();
while(it.hasNext()){
Map.Entry item=(Map.Entry)it.next();
//it.remove()将从映射中删除该项
大多数
集合
类都不允许在使用时((Integer)item.getValue()在迭代该
集合
时修改。Java库将在迭代过程中尝试修改
集合
称为“并发修改”。不幸的是,这表明唯一可能的原因是由多个线程同时修改,但事实并非如此。仅使用一个线程就可以为
集合
(使用或an)创建迭代器,开始迭代(使用或等效地进入增强的
for
循环的主体),修改
集合
,然后继续迭代

为了帮助程序员,这些
集合
类的一些实现尝试检测错误的并发修改,如果检测到,则抛出一个
ConcurrentModificationException
。然而,一般来说,保证检测所有并发修改是不可能的,也是不实际的。因此<代码>集合
并不总是导致引发ConcurrentModificationException

文件说明:

当对象的并发修改不允许时,检测到该修改的方法可能会引发此异常

请注意,此异常并不总是表示对象已被其他线程并发修改。如果单个线程发出的方法调用序列违反了对象的约定,则该对象可能会引发此异常

请注意,fail fast行为无法得到保证,因为一般来说,在存在非同步并发修改的情况下,无法做出任何硬保证。fail fast操作会尽最大努力抛出ConcurrentModificationException

注意

  • 异常可能会被抛出,而非必须被抛出
  • 不需要不同的线程
  • 无法保证引发异常
  • 抛出异常是在尽最大努力的基础上进行的
  • 抛出异常会发生
和类的文档说明如下:

[直接或间接从此类]返回的迭代器是快速失败的:如果[collection]在创建迭代器后的任何时候进行修改,
iterator
会抛出一个
ConcurrentModificationException
。因此,在并发修改面前,迭代器会快速、干净地失败,而不是冒着不确定的任意行为风险确定未来的时间

请注意,无法保证迭代器的fail-fast行为,因为一般来说,在存在非同步并发修改的情况下,不可能做出任何硬保证。fail-fast迭代器会尽最大努力抛出
ConcurrentModificationException
。因此,编写一个我对这个异常的正确性表示怀疑:迭代器的fail fast行为应该只用于检测bug

再次注意,行为“无法保证”,只是“尽最大努力”

接口的几种方法的文档说明如下:

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

再次注意,检测只需要“尽力而为”,而
ConcurrentModificationException
仅明确建议用于非conc
map.keySet().removeIf(key -> key condition);