java 并发修改异常
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1089546/
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
Concurrent Modification Exception
提问by Cambium
I'm currently working on a multi-threaded application, and I occasionally receive a concurrently modification exception (approximately once or twice an hour on average, but occurring at seemingly random intervals).
我目前正在开发一个多线程应用程序,我偶尔会收到一个并发修改异常(平均每小时大约一两次,但以看似随机的时间间隔发生)。
The faulty class is essentially a wrapper for a map -- which extends LinkedHashMap(with accessOrder set to true). The class has a few methods:
错误的类本质上是映射的包装器——它扩展了LinkedHashMap(accessOrder 设置为 true)。该类有几个方法:
synchronized set(SomeKey key, SomeValue val)
The set method adds a key/value pair to the internal map, and is protected by the synchronized keyword.
set 方法将键/值对添加到内部映射中,并由 synchronized 关键字保护。
synchronized get(SomeKey key)
The get method returns the value based on the input key.
get 方法根据输入键返回值。
rebuild()
The internal map is rebuilt once in a while (~every 2 minutes, intervals do not match up with the exceptions). The rebuild method essentially rebuilds the values based on their keys. Since rebuild() is fairly expensive, I did not put a synchronized keyword on the method. Instead, I am doing:
内部地图会不时重建一次(~每 2 分钟一次,间隔与异常不匹配)。重建方法本质上是根据它们的键重建值。由于rebuild() 相当昂贵,我没有在方法上放置synchronized 关键字。相反,我正在做:
public void rebuild(){
/* initialization stuff */
List<SomeKey> keysCopy = new ArrayList<SomeKey>();
synchronized (this) {
keysCopy.addAll(internalMap.keySet());
}
/*
do stuff with keysCopy, update a temporary map
*/
synchronized (this) {
internalMap.putAll(tempMap);
}
}
The exception occurs at
异常发生在
keysCopy.addAll(internalMap.keySet());
Inside the synchronized block.
在同步块内。
Suggestions are greatly appreciated. Feel free to point me to specific pages/chapters in Effective Javaand/or Concurrency in Practice.
非常感谢您的建议。请随时向我指出Effective Java和/或Concurrency in Practice 中的特定页面/章节。
Update 1:
更新 1:
Sanitized stacktrace:
消毒的堆栈跟踪:
java.util.ConcurrentModificationException
at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(LinkedHashMap.java:365)
at java.util.LinkedHashMap$KeyIterator.next(LinkedHashMap.java:376)
at java.util.AbstractCollection.toArray(AbstractCollection.java:126)
at java.util.ArrayList.addAll(ArrayList.java:473)
at a.b.c.etc.SomeWrapper.rebuild(SomeWraper.java:109)
at a.b.c.etc.SomeCaller.updateCache(SomeCaller.java:421)
...
Update 2:
更新 2:
Thanks everyone for the answers so far. I think the problem lies within the LinkedHashMap and its accessOrder attribute, although I am not entirely certain atm (investigating).
感谢大家到目前为止的答案。我认为问题出在 LinkedHashMap 及其 accessOrder 属性中,尽管我并不完全确定 atm(正在调查)。
If accessOrder on a LinkedHashMap is set to true, and I access its keySet then proceed to add the keySet to a linkedList via addAll, do either of these actions mutate the order (i.e. count towards an "access")?
如果 LinkedHashMap 上的 accessOrder 设置为 true,并且我访问其 keySet,然后继续通过 addAll 将 keySet 添加到链接列表,这些操作中的任何一个是否会改变顺序(即计入“访问”)?
采纳答案by Sean McCauliff
If you constructed LinkedHashMap with accessOrder = true then LinkedHashMap.get() actually mutates the LinkedHashMap since it stores the most recently accessed entry at the front of the linked list of entries. Perhaps something is calling get() while the array list is making its copy with the Iterator.
如果您使用 accessOrder = true 构造 LinkedHashMap,则 LinkedHashMap.get() 实际上会改变 LinkedHashMap,因为它将最近访问的条目存储在条目链表的前面。当数组列表使用迭代器进行复制时,可能有什么东西在调用 get() 。
回答by gubby
This exception does not normally have anything to do with synchronization - it is normally thrown if a a Collection is modified while an Iterator is iterating through it. AddAll methods may use an iterator - and its worth noting that the posh foreach loop iterates over instances of Iterator too.
这个异常通常与同步没有任何关系——如果在迭代器迭代它时修改了一个集合,它通常会被抛出。AddAll 方法可以使用迭代器 - 值得注意的是,华丽的 foreach 循环也会迭代 Iterator 的实例。
e.g:
例如:
for(Object o : objects) {
objects.remove(o);
}
is sufficient to get the exception on some collections (e.g ArrayList).
足以在某些集合(例如 ArrayList)上获得异常。
James
詹姆士
回答by Artem Barger
Are those all function you have in your wrapper? Because this exception could be thrown while you somehow iterating over collection in another place. And I guess you synchronized method with potential obvious race condition, but probably have missed less obvious cases. Herethe reference to exception class docs.
这些都是你的包装器中的功能吗?因为当您以某种方式在另一个地方迭代集合时,可能会抛出此异常。我猜你同步方法与潜在的明显竞争条件,但可能错过了不太明显的情况。这里是对异常类文档的引用。
回答by Tim Bender
From the Javadoc:
来自 Javadoc:
If multiple threads access a linked hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally. This is typically accomplished by synchronizing on some object that naturally encapsulates the map. If no such object exists, the map should be "wrapped" using the Collections.synchronizedMap method. This is best done at creation time, to prevent accidental unsynchronized access to the map:
Map m = Collections.synchronizedMap(new LinkedHashMap(...));
如果多个线程并发访问链接的哈希映射,并且至少有一个线程在结构上修改映射,则必须在外部进行同步。这通常是通过同步一些自然封装地图的对象来完成的。如果不存在此类对象,则应使用 Collections.synchronizedMap 方法“包装”该地图。这最好在创建时完成,以防止对地图的意外不同步访问:
Map m = Collections.synchronizedMap(new LinkedHashMap(...));
It may be safer for you to actually wrap the LinkedHashMap rather than claim you extend it. So your implementation would have an internal data member which is the Map returned by Collections.synchronizedMap(new LinkedHashMap(...)).
对您来说,实际包装 LinkedHashMap 而不是声称您扩展它可能更安全。所以你的实现会有一个内部数据成员,它是 Collections.synchronizedMap(new LinkedHashMap(...)) 返回的 Map。
Please see the Collections javadoc for details: Collections.synchronizedMap
有关详细信息,请参阅 Collections javadoc:Collections.synchronizedMap
回答by Tim Bender
Keep access to internalMap syncronized, otherwise java.util.ConcurrentModificationException occurs because HashMap#modCount (which records structural changes) can be changed concurrently during keyset iteration (due keysCopy.addAll(internalMap.keySet() invocation).
保持对 internalMap syncronized 的访问,否则会发生 java.util.ConcurrentModificationException 因为 HashMap#modCount(记录结构变化)可以在 keyset 迭代期间同时更改(由于 keysCopy.addAll(internalMap.keySet() 调用)。
LinkedHashMap javaDoc specifies : "In access-ordered linked hash maps, merely querying the map with getis a structural modification."
LinkedHashMap javaDoc 指定:“在按访问顺序的链接哈希映射中,仅使用get查询映射是一种结构修改。”
回答by Stephen Denne
Is internalMap static? You may have multiple objects, each locking on this, the object, but not providing correct locking on your static internalMap.
internalMap 是静态的吗?您可能有多个对象,每个对象都锁定在this该对象上,但没有在您的静态 internalMap 上提供正确的锁定。
回答by akf
that is strange. I cant see any way that the exception is being thrown at the location you are identifying (in the default implmentation in Java 6). are you getting a ConcurrentModificationException in the ArrayList or the HashMap? have you overridden the keySet()method in your HashMap?
这很奇怪。我看不到在您识别的位置抛出异常的任何方式(在 Java 6 的默认实现中)。您是否在 ArrayList 或 HashMap 中收到 ConcurrentModificationException?你覆盖了你的keySet()方法HashMap吗?
editmy mistake - the ArrayList will force the KeySet to iterate (AbstractCollection.toArray() will iterate over its keys)
编辑我的错误 - ArrayList 将强制 KeySet 进行迭代(AbstractCollection.toArray() 将迭代其键)
there is somewhere in the code that is allowing you to update your internal map that is not synchronized.
代码中的某处允许您更新未同步的内部地图。
- do you have a utility method that exposes your internal map that "shouldnt be used" or
- is your internal map identified as public scope
- 您是否有一种实用方法可以公开“不应使用”的内部地图,或者
- 您的内部地图是否标识为公共范围
回答by Chochos
Try removing the synchronized keyword from the set() and get() methods, and instead use a synchronized block inside the method, locking on internalMap; then change the synchronized blocks on the rebuild() method to lock on the internalMap as well.
尝试从 set() 和 get() 方法中删除 synchronized 关键字,而是在方法内部使用同步块,锁定 internalMap;然后更改rebuild() 方法上的同步块以锁定internalMap。
回答by Hyman Leow
I don't think your set()/get()are synchronized against the same monitor that rebuild()is. This makes it possible for someone to call set/get while the problematic line is being executed, specifically when iterating over they key set of the internalMap during the addAll()call (internal implementation that's exposed through your stack trace).
我不认为您的set()/get()与同一台显示器同步rebuild()。这使得有人可以在执行有问题的行时调用 set/get,特别是在addAll()调用期间迭代 internalMap 的键集时(通过堆栈跟踪公开的内部实现)。
Instead of making set()synchronized, have you tried something along the lines of:
set()您是否尝试过以下方法而不是同步:
public void set(SomeKey key, SomeValue val) {
synchronized(this) {
internalMap.put(key, val); // or whatever your get looks like
}
}
I don't think you need to synchronize get()at all, but if you insist:
我认为您根本不需要同步get(),但如果您坚持:
public SomeValue get(SomeKey key) {
synchronized(this) {
internalMap.get(key); // or whatever your get looks like
}
}
In fact, I think you're better off synchronizing against internalMapinstead of this. It also doesn't hurt to make internalMapvolatile, though I don't think this is really necessary if you're sure that set()/get()/rebuild()are the only methods directly accessing internalMap, and they are all accessing it in a synchronized manner.
事实上,我认为你最好同步internalMap而不是this. 它也不会伤害到化妆internalMapvolatile,但我不认为这是真的有必要,如果你确信set()/ get()/rebuild()直接访问internalMap的唯一方法,而且都是访问它以同步的方式。
private volatile Map internalMap ...
回答by Stephen Denne
While iterating over the keys, due to
在迭代键时,由于
keysCopy.addAll(internalMap.keySet());
Do you decide that some of the entries can be removed from the LinkedHashMap?
您是否决定可以从 LinkedHashMap 中删除某些条目?
The removeEldestEntrymethod could be either returning true, or modifying the map, thereby upsetting the iteration.
该removeEldestEntry方法可以是任一返回true,或修改地图,从而扰乱迭代。

