我正在使用JPA持久化对象.Main对象与另一个对象具有一个拥有的One-Many关系.另一个对象存储在HashMap中.什么样的同步可以解决这个问题?它似乎发生在完全随机的时间,并且非常难以预测.这是我得到的例外:
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)
Robin.. 249
这不是同步问题.如果正在迭代的基础集合被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()操作.
这不是同步问题.如果正在迭代的基础集合被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()操作.
尝试使用ConcurrentHashMap而不是普通的HashMap
的变形Collection
通过,尽管迭代Collection
使用Iterator
被不允许被大多数的Collection
类。Java库将尝试在其上Collection
进行迭代的同时修改的尝试称为“并行修改”,不幸的是,这表明唯一可能的原因是多个线程同时进行修改,但事实并非如此。仅使用一个线程就可以为Collection
(使用Collection.iterator()
或增强的for
循环)创建一个迭代器,开始进行迭代(使用Iterator.next()
或等效地进入增强的for
循环的主体),修改Collection
,然后继续进行迭代。
为了帮助程序员,这些类的某些实现尝试检测错误的并发修改,并在检测到错误时将其抛出。但是,通常不可能和实际地保证检测到所有并发的修改。因此,错误地使用并不总是会导致抛出错误。Collection
ConcurrentModificationException
Collection
ConcurrentModificationException
的文档ConcurrentModificationException
说:
当不允许对对象进行并发修改时,检测到该对象的并发修改的方法可能会引发此异常。
请注意,此异常并不总是表示对象已被其他线程同时修改。如果单个线程发出违反对象约定的方法调用序列,则对象可能会抛出此异常...
请注意,不能保证快速故障行为,因为通常来说,在存在不同步的并发修改的情况下,不可能做出任何严格的保证。快速失败的操作
ConcurrentModificationException
是尽力而为的。
注意
异常可能被抛出,而不是必须抛出
不需要不同的线程
不能保证抛出异常
尽最大努力抛出异常
当检测到并发修改时引发异常,而不是引起异常
的文档HashSet
,HashMap
,TreeSet
和ArrayList
类这样说:
从该类直接或间接返回的迭代器是快速失败的:如果在创建迭代器后的任何时间修改[collection],则除了通过迭代器自己的remove方法之外,都以任何方式对其进行
Iterator
抛出ConcurrentModificationException
。因此,面对并发修改,迭代器将迅速而干净地失败,而不是冒着在未来不确定的时间冒任意,不确定的行为的风险。注意,迭代器的快速失败行为无法得到保证,因为通常来说,在存在不同步的并发修改的情况下,不可能做出任何严格的保证。快速失败的迭代器会
ConcurrentModificationException
尽力而为。因此,编写依赖于此异常的程序的正确性是错误的:迭代器的快速失败行为应仅用于检测错误。
再次注意,行为“无法保证”,而仅仅是“尽力而为”。
Map
接口的几种方法的文档说:
非并行实现应重写此方法,并在尽力而为的基础上,
ConcurrentModificationException
如果在计算过程中检测到映射函数修改了此映射,则抛出。并发实现应重写此方法,并在尽力而为的基础上,IllegalStateException
如果检测到映射函数在计算过程中修改了此映射,则抛出该错误,因此计算将永远不会完成。
再次注意,检测仅需要“尽力而为”,并且ConcurrentModificationException
仅针对非并发(非线程安全)类明确建议使用a 。
ConcurrentModificationException
因此,当您看到由a引起的堆栈跟踪时ConcurrentModificationException
,您不能立即假定原因是对a的不安全多线程访问Collection
。您必须检查堆栈跟踪,以确定哪个类Collection
引发了异常(该类的方法将直接或间接引发该异常)以及哪个Collection
对象。然后,您必须检查可从何处修改该对象。
最常见的原因是Collection
在上的增强for
循环中修改了Collection
。仅仅因为您没有Iterator
在源代码中看到对象并不意味着就没有对象Iterator
!幸运的是,错误for
循环的语句之一通常位于堆栈跟踪中,因此通常可以很容易地找到错误。
一个更棘手的情况是您的代码传递对Collection
对象的引用时。请注意,集合的不可修改视图(例如由产生的Collections.unmodifiableList()
)保留了对可修改集合的引用,因此在“不可修改”集合上进行迭代可能会引发异常(修改已在其他地方完成)。您的其他视图(Collection
例如子列表,Map
条目集和Map
键集)也保留了对原始(可修改)的引用Collection
。即使对于线程安全的Collection
(例如),这也可能是一个问题CopyOnWriteList
。不要以为线程安全(并发)collectins永远不会抛出异常。
Collection
在某些情况下,哪些操作可以修改a 可能是意外的。例如,LinkedHashMap.get()
修改其collection。
最困难的情况是异常是由于多个线程同时进行修改而导致的。
编程以防止并发修改错误
在可能的情况下,将所有引用限制在一个Collection
对象上,以便更容易防止并发修改。使Collection
一个private
物体或一个局部变量,也不要到返回引用Collection
的方法或它的迭代器。这样一来,检查可以修改的所有地方就容易得多Collection
。如果Collection
要由多个线程使用,则可以确保Collection
通过适当的同步和锁定来仅访问线程。