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

时间:2009-03-02 14:59:54

标签: java collections concurrentmodification

我正在使用Collection(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)

8 个答案:

答案 0 :(得分:240)

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

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()操作。

答案 1 :(得分:56)

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

答案 2 :(得分:8)

大多数Collection类不允许修改Collection遍历Collection。 Java库在尝试对Collection进行迭代的同时进行了“并发修改”,这不幸地表明,唯一可能的原因是多个线程同时进行了修改,但事实并非如此。仅使用一个线程就可以为Collection创建迭代器(使用IteratorCollection.iterator()),开始迭代(使用enhanced for loop或等效地输入主体)增强for循环),修改Collection,然后继续进行迭代。

为帮助程序员,请对那些Collection attempt 一些实现进行检测,以检测错误的并发修改,并在检测到错误时抛出ConcurrentModificationException 。但是,通常不可能和实际地保证检测到所有并发的修改。因此,错误地使用Collection并不总是导致抛出ConcurrentModificationException

Iterator.next()的文档中说:

  

检测到对象并发修改的方法可能不允许抛出此异常...

     

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

     

请注意,不能保证快速故障行为,因为通常来说,在存在不同步的并发修改的情况下,不可能做出任何严格的保证。失败快速操作会尽最大努力抛出ConcurrentModificationException

请注意

  • 可能会抛出异常 ,而不是必须
  • 不需要不同的线程
  • 不能保证引发异常
  • 引发异常是尽力而为
  • 引发异常发生ConcurrentModificationException

when the concurrent modification is detected, not when it is causedHashSetHashMapTreeSet类的文档说:

  

[直接或间接从此类返回的迭代器]是快速失败的:如果在创建迭代器后的任何时间修改[collection],则可以通过迭代器自己的remove方法,{{1 }}引发Iterator。因此,面对并发修改,迭代器会快速干净地失败,而不会在未来的不确定时间内冒任意,不确定的行为的风险。

     

请注意,不能保证迭代器的快速失败行为,因为通常来说,在存在不同步的并发修改的情况下,不可能做出任何严格的保证。快速失败的迭代器会尽最大努力抛出ConcurrentModificationException。因此,编写依赖于此异常的程序以确保其正确性是错误的:迭代器的快速失败行为应仅用于检测错误

再次请注意,该行为“无法保证”,而只是“尽力而为”。

ArrayList界面的几种方法的文档中说:

  

非并行实现应重写此方法,并在尽力而为的基础上,如果检测到映射函数在计算期间修改了此映射,则抛出ConcurrentModificationException。并发实现应重写此方法,并在尽力而为的基础上,如果在计算过程中检测到映射函数修改了此映射,则抛出ConcurrentModificationException,结果计算将永远无法完成。

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

调试ConcurrentModificationException

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

  • 最常见的原因是在Collection上的增强Collection循环中修改for。仅仅因为您在源代码中没有看到Collection对象,并不意味着那里没有Iterator!幸运的是,有故障的Iterator循环中的一条语句通常会出现在堆栈跟踪中,因此通常很容易找到错误。
  • 一个更棘手的情况是您的代码传递对for对象的引用。请注意,集合的不可修改视图(例如examine the stack-trace产生的)保留了对可修改集合的引用,因此Collections.unmodifiableList()(修改已在其他地方完成)。您Collection的其他视图(例如iteration over an "unmodifiable" collection can throw the exceptionsub listsMap entry sets)也保留了对原始Collection的引用。即使对于线程安全的Collection,例如Map key sets,这也可能是一个问题。不要以为线程安全(并发)collectins永远不会抛出异常。
  • 在某些情况下,哪些操作可以修改Collection可能是意外的。例如,CopyOnWriteList
  • 最困难的情况是由于多个线程同时进行修改而导致异常

编程以防止并发修改错误

在可能的情况下,将所有引用限制为Collection对象,因此可以更容易地防止并发修改。使CollectionCollection对象或局部变量,并且不要从方法中返回对private或其迭代器的引用。这样一来,检查 all 可以修改Collection的地方就容易得多。如果Collection将由多个线程使用,则可以确保仅通过适当的同步和锁定,这些线程才能访问Collection

答案 3 :(得分:2)

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

我不知道在所有持久化类中添加一个版本是否会将其排序,但这是Hibernate可以提供对表中行的独占访问的一种方式。

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

答案 4 :(得分:1)

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

map.keySet().removeIf(key -> key condition);

答案 5 :(得分:0)

根据您的尝试,尝试CopyOnWriteArrayList或CopyOnWriteArraySet。

答案 6 :(得分: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();
    }

答案 7 :(得分:0)

当尝试从列表中删除x个最后一项时,我遇到了此异常。 **If You Have Created Your Custom Field at the checkout page** add_action( 'woocommerce_after_checkout_validation', 'shipping_time_optionss', 9999, 2); function shipping_time_optionss( $fields, $errors ){ // if any validation errors if ( empty( $_POST['woo_shipping_time'] ) ) { $errors->add( 'woocommerce_password_error', __( 'Please Select Shipping Time Option.' ) ); } } 是唯一对我有用的解决方案。