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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/602636/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 16:43:40  来源:igfitidea点击:

Why is a ConcurrentModificationException thrown and how to debug it

javaexceptioncollectionsconcurrentmodification

提问by mainstringargs

I am using a Collection(a HashMapused indirectly by the JPA, it so happens), but apparently randomly the code throws a ConcurrentModificationException. What is causing it and how do I fix this problem? By using some synchronization, perhaps?

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

Here is the full stack-trace:

这是完整的堆栈跟踪:

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)

采纳答案by Robin

This is not a synchronization problem. This will occur if the underlying collection that is being iterated over is modified by anything other than the Iterator itself.

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

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

This will throw a ConcurrentModificationException when the it.hasNext() is called the second time.

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

The correct approach would be

正确的做法是

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

Assuming this iterator supports the remove() operation.

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

回答by duffymo

It sounds less like a Java synchronization issue and more like a database locking problem.

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

I don't know if adding a version to all your persistent classes will sort it out, but that's one way that Hibernate can provide exclusive access to rows in a table.

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

Could be that isolation level needs to be higher. If you allow "dirty reads", maybe you need to bump up to serializable.

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

回答by Javamann

Try either CopyOnWriteArrayList or CopyOnWriteArraySet depending on what you are trying to do.

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

回答by Chochos

Try using a ConcurrentHashMap instead of a plain HashMap

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

回答by ZhaoGang

Note that the selected answer cannot be applied to your context directly before some modification, if you are trying to remove some entries from the map while iterating the map just like me.

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

I just give my working example here for newbies to save their time:

我只是在这里给出我的工作示例,供新手节省时间:

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();
    }

回答by Raedwald

Modification of a Collectionwhile iterating through that Collectionusing an Iteratoris not permittedby most of the Collectionclasses. The Java library calls an attempt to modify a Collectionwhile iterating through it a "concurrent modification". That unfortunately suggests the only possible cause is simultaneous modification by multiple threads, but that is not so. Using only one thread it is possible to create an iterator for the Collection(using Collection.iterator(), or an enhanced forloop), start iterating (using Iterator.next(), or equivalently entering the body of the enhanced forloop), modify the Collection, then continue iterating.

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

To help programmers, someimplementations of those Collectionclasses attemptto detect erroneous concurrent modification, and throw a ConcurrentModificationExceptionif they detect it. However, it is in general not possible and practical to guarantee detection of all concurrent modifications. So erroneous use of the Collectiondoes not always result in a thrown ConcurrentModificationException.

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

The documentation of ConcurrentModificationExceptionsays:

的文档ConcurrentModificationException说:

This exception may be thrown by methods that have detected concurrent modification of an object when such modification is not permissible...

Note that this exception does not always indicate that an object has been concurrently modified by a different thread. If a single thread issues a sequence of method invocations that violates the contract of an object, the object may throw this exception...

Note that fail-fast behavior cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast operations throw ConcurrentModificationExceptionon a best-effort basis.

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

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

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

Note that

注意

The documentation of the HashSet, HashMap, TreeSetand ArrayListclasses says this:

的文档HashSetHashMapTreeSetArrayList类这样说:

The iterators returned [directly or indirectly from this class] are fail-fast: if the [collection] is modified at any time after the iterator is created, in any way except through the iterator's own remove method, the Iteratorthrows a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.

Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationExceptionon a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.

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

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

Note again that the behaviour "cannot be guaranteed" and is only "on a best-effort basis".

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

The documentation of several methods of the Mapinterface say this:

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

Non-concurrent implementations should override this method and, on a best-effort basis, throw a ConcurrentModificationExceptionif it is detected that the mapping function modifies this map during computation. Concurrent implementations should override this method and, on a best-effort basis, throw an IllegalStateExceptionif it is detected that the mapping function modifies this map during computation and as a result computation would never complete.

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

Note again that only a "best-effort basis" is required for detection, and a ConcurrentModificationExceptionis explicitly suggested only for the non concurrent (non thread-safe) classes.

再次注意,检测只需要“尽力而为”,并且 aConcurrentModificationException仅明确建议用于非并发(非线程安全)类。

Debugging ConcurrentModificationException

调试 ConcurrentModificationException

So, when you see a stack-trace due to a ConcurrentModificationException, you can not immediately assume that the cause is unsafe multi-threaded access to a Collection. You must examine the stack-traceto determine which class of Collectionthrew the exception (a method of the class will have directly or indirectly thrown it), and for which Collectionobject. Then you must examine from where that object can be modified.

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

  • The most common cause is modification of the Collectionwithin an enhanced forloop over the Collection. Just because you do not see an Iteratorobject in your source code does not mean there is no Iteratorthere! Fortunately, one of the statements of the faulty forloop will usually be in the stack-trace, so tracking down the error is usually easy.
  • A trickier case is when your code passes around references to the Collectionobject. Note that unmodifiableviews of collections (such as produced by Collections.unmodifiableList()) retain a reference to the modifiable collection, so iteration over an "unmodifiable" collection can throw the exception(the modification has been done elsewhere). Other viewsof your Collection, such as sub lists, Mapentry setsand Mapkey setsalso retain references to the original (modifiable) Collection. This can be a problem even for a thread-safe Collection, such as CopyOnWriteList; do not assume that thread-safe (concurrent) collections can never throw the exception.
  • Which operations can modify a Collectioncan be unexpected in some cases. For example, LinkedHashMap.get()modifies its collection.
  • The hardest cases are when the exception isdue to concurrent modification by multiple threads.
  • 最常见的原因是对变形例Collection的增强内for整个环Collection。仅仅因为您Iterator在源代码中没有看到对象并不意味着不Iterator存在!幸运的是,错误for循环的语句之一通常会在堆栈跟踪中,因此跟踪错误通常很容易。
  • 更棘手的情况是当您的代码传递对Collection对象的引用时。请注意,集合的不可修改视图(例如由 生成Collections.unmodifiableList())保留对可修改集合的引用,因此对“不可修改”集合的迭代可能会引发异常(修改已在其他地方完成)。您的其他视图Collection,例如子列表Map条目集Map键集,也保留对原始(可修改)的引用Collection。即使对于线程安全的Collection,这也可能是一个问题,例如CopyOnWriteList; 不要假设线程安全(并发)集合永远不会抛出异常。
  • Collection在某些情况下,哪些操作可以修改 a可能是意外的。例如,LinkedHashMap.get()修改其集合
  • 最困难的情况是异常由于多个线程并发修改造成的。

Programming to prevent concurrent modification errors

防止并发修改错误的编程

When possible, confine all references to a Collectionobject, so its is easier to prevent concurrent modifications. Make the Collectiona privateobject or a local variable, and do not return references to the Collectionor its iterators from methods. It is then much easier to examine allthe places where the Collectioncan be modified. If the Collectionis to be used by multiple threads, it is then practical to ensure that the threads access the Collectiononly with appropriate synchonization and locking.

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

回答by Zentopia

In Java 8, you can use lambda expression:

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

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