关于java:为什么抛出ConcurrentModificationException以及如何调试它

Why is a ConcurrentModificationException thrown and how to debug it

我使用的是JPA间接使用的Collection(一个HashMap,它是这样发生的),但显然是随机的代码抛出了ConcurrentModificationException。是什么导致的?我该如何解决这个问题?也许是通过使用一些同步?

以下是完整的堆栈跟踪:

1
2
3
4
5
6
7
8
9
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)


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

1
2
3
4
5
6
Iterator it = map.entrySet().iterator();
while (it.hasNext())
{
   Entry item = it.next();
   map.remove(item.getKey());
}

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

正确的方法是

1
2
3
4
5
6
   Iterator it = map.entrySet().iterator();
   while (it.hasNext())
   {
      Entry item = it.next();
      it.remove();
   }

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


尝试使用Concurrenthashmap而不是普通的hashmap


Iterator来编辑Collection时,绝大多数Collection类不允许编辑。爪哇图书馆称之为试图通过一个"并行修改"来修改Collection时,非偶然提出的唯一原因是同时通过多个线程进行修改,但这不是这样。在一个可能的范围内,可以为Collection(使用EDOCX1〕或增强forLOOP)创建一个ITERATOR(使用for),启动ITERATING(使用Iterator.next(),或等效地ENTERING the BODY of ENHANHANCED forLOP,Modify the Collection,then

为了帮助编程人员,这些Collection的一些执行情况正在试图检测恐怖主义并行修正,并通过ConcurrentModificationException进行检测。然而,一般来说,这是不可能和实际的,以确保检测所有竞合变化。因此,使用Collection并不总是导致ConcurrentModificationException

The documentation of ConcurrentModificationExceptionsays:

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.

  • 例外可能被击中,而不一定被击中。
  • 不需要不同的螺纹
  • Throwing the exception cannot be guaranteed
  • 推进例外是一个最好的基础
  • Throwing the exceptions happens when the contral modification is detected,not when it's caused

1.The documentation of the HashSetHashMapTreeSetand ArrayListclasses says:

BLCK1/

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

The documentation of several methods of the EDOCX1&20)Interface say this:

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 an IllegalStateException if it is detected that the mapping function modifies this map during computation and as a result computation would never complete.

又注意到只有一种"最佳努力基础"是检测所必需的,而一种ConcurrentModificationException则仅建议非竞争者(非威胁安全)类别。

EDOCX1&12

所以,当你看到一个由ConcurrentModificationException引起的堆栈痕迹时,你不能马上认定原因是不安全的多线程访问Collection。你必须检查堆栈的痕迹,以确定哪些种类的Collection除此之外(该种类的一种方法将直接或间接地通过它),以及哪些种类的Collection物体。然后你必须从可以修改对象的地方来考虑。

  • 最常见的原因是Collection的改变,在for上环绕着Collection。因为你看不到源代码中的一个对象不代表没有偶然地,一个故障的说明,EDOCX1&7)LOOP通常会出现在堆栈的轨迹中,因此,错误的追踪通常是容易的。
  • 一个骗子的例子是当你的代码绕着Collection号对象传递时。Note that unmodificable views of collections(such as produced by EDOCX1&34).Retain a reference to the modificable collection,so iteration over an"unmodificable"collection can throw the exception(the modification has been elsewhere).Other views of your Collection,sub lists,Mapentry sets and Mapkey sets also retain references to the original(remodible)Collection这可能是一个问题,甚至是一个安全的Collection,例如CopyOnWriteList;不要认为安全的(竞争者)集群永远不会被排除在外。
  • 在某些情况下,可不期望操作的Collection。修改它的收藏。
  • 硬案例是由于多径并行修改而引起的例外情况。

防止并行修正错误的编程

在可能的情况下,将所有的参考资料都限定在一个CollectionObject上,所以它很容易防止竞争性更改。将对象或局部变量转换为Collection或方法中的ITERTORS。此后,很容易检查所有可以修改Collection的地方。如果Collection被多个线程使用,则该线程实际上只能与适当的同步和锁定相关联。


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

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

可能是隔离级别需要更高。如果您允许"脏读",也许您需要进行串行化。


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.

我只是举个例子,让新手节省时间:

1
2
3
4
5
6
7
8
9
10
11
12
13
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();
    }

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