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
Why is a ConcurrentModificationException thrown and how to debug it
提问by mainstringargs
I am using a Collection
(a HashMap
used 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?
我正在使用一个Collection
(HashMap
由 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 Collection
while iterating through that Collection
using an Iterator
is not permittedby most of the Collection
classes. The Java library calls an attempt to modify a Collection
while 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 for
loop), start iterating (using Iterator.next()
, or equivalently entering the body of the enhanced for
loop), modify the Collection
, then continue iterating.
的变形Collection
通过,尽管迭代Collection
使用Iterator
被不允许被大多数的Collection
类。Java 库将尝试修改 a Collection
while 迭代它称为“并发修改”。不幸的是,这表明唯一可能的原因是多个线程同时修改,但事实并非如此。仅使用一个线程就可以为Collection
(使用Collection.iterator()
,或增强for
循环)创建迭代器,开始迭代(使用Iterator.next()
,或等效地进入增强for
循环的主体),修改Collection
,然后继续迭代。
To help programmers, someimplementations of those Collection
classes attemptto detect erroneous concurrent modification, and throw a ConcurrentModificationException
if they detect it. However, it is in general not possible and practical to guarantee detection of all concurrent modifications. So erroneous use of the Collection
does not always result in a thrown ConcurrentModificationException
.
为了帮助程序员,这些类的一些实现试图检测错误的并发修改,并在检测到时抛出 a 。然而,通常不可能保证检测到所有并发修改。因此错误地使用并不总是导致抛出.Collection
ConcurrentModificationException
Collection
ConcurrentModificationException
The documentation of ConcurrentModificationException
says:
的文档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
ConcurrentModificationException
on a best-effort basis.
当不允许此类修改时,检测到对象的并发修改的方法可能会抛出此异常......
请注意,此异常并不总是表示对象已被不同线程同时修改。如果单个线程发出一系列违反对象约定的方法调用,则该对象可能会抛出此异常...
请注意,不能保证快速失败行为,因为一般来说,在存在非同步并发修改的情况下不可能做出任何硬保证。快速失败的操作是
ConcurrentModificationException
在尽力而为的基础上进行的。
Note that
注意
- the exception maybe throw, not mustbe thrown
- different threads are not required
- throwing the exception cannot be guaranteed
- throwing the exception is on a best-effort basis
- throwing the exception happens when the concurrent modification is detected, not when it is caused
- 异常可以抛出,而不是必须抛出
- 不需要不同的线程
- 不能保证抛出异常
- 抛出异常是尽最大努力的
- 引发异常情况发生时的并发修改时检测,而不是当它引起的
The documentation of the HashSet
, HashMap
, TreeSet
and ArrayList
classes says this:
的文档HashSet
,HashMap
,TreeSet
和ArrayList
类这样说:
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
Iterator
throws aConcurrentModificationException
. 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
ConcurrentModificationException
on 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 Map
interface say this:
Map
接口的几种方法的文档是这样说的:
Non-concurrent implementations should override this method and, on a best-effort basis, throw a
ConcurrentModificationException
if 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 anIllegalStateException
if 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 ConcurrentModificationException
is 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 Collection
threw the exception (a method of the class will have directly or indirectly thrown it), and for which Collection
object. 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
Collection
within an enhancedfor
loop over theCollection
. Just because you do not see anIterator
object in your source code does not mean there is noIterator
there! Fortunately, one of the statements of the faultyfor
loop 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
Collection
object. Note that unmodifiableviews of collections (such as produced byCollections.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 yourCollection
, such as sub lists,Map
entry setsandMap
key setsalso retain references to the original (modifiable)Collection
. This can be a problem even for a thread-safeCollection
, such asCopyOnWriteList
; do not assume that thread-safe (concurrent) collections can never throw the exception. - Which operations can modify a
Collection
can 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 Collection
object, so its is easier to prevent concurrent modifications. Make the Collection
a private
object or a local variable, and do not return references to the Collection
or its iterators from methods. It is then much easier to examine allthe places where the Collection
can be modified. If the Collection
is to be used by multiple threads, it is then practical to ensure that the threads access the Collection
only with appropriate synchonization and locking.
如果可能,限制对一个Collection
对象的所有引用,这样更容易防止并发修改。使Collection
aprivate
对象或局部变量,并且不Collection
从方法返回对或其迭代器的引用。然后检查可以修改的所有位置要容易得多Collection
。如果Collection
要由多个线程使用,那么确保线程Collection
仅通过适当的同步和锁定访问 是可行的。
回答by Zentopia
In Java 8, you can use lambda expression:
在 Java 8 中,您可以使用 lambda 表达式:
map.keySet().removeIf(key -> key condition);