java 带有 LinkedHashMap 的 ConcurrentModificationException
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16180568/
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
ConcurrentModificationException with LinkedHashMap
提问by Aditya Sakhuja
Not sure what is triggering a java.util.ConcurrentModificationException
when I iterate over the LinkedHashMap
structure in the code below. Using the Map.Entry
approach works fine. Did not get a good explanation on what is triggering this from the previous posts.
java.util.ConcurrentModificationException
当我遍历LinkedHashMap
下面代码中的结构时,不确定是什么触发了 a 。使用该Map.Entry
方法工作正常。没有从以前的帖子中得到关于是什么触发这个的很好的解释。
Any help would be appreciated.
任何帮助,将不胜感激。
import java.util.LinkedHashMap;
import java.util.Map;
public class LRU {
// private Map<String,Integer> m = new HashMap<String,Integer>();
// private SortedMap<String,Integer> lru_cache = Collections.synchronizedSortedMap(new TreeMap<String, Integer>());
private static final int MAX_SIZE = 3;
private LinkedHashMap<String,Integer> lru_cache = new LinkedHashMap<String,Integer>(MAX_SIZE, 0.1F, true){
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return(lru_cache.size() > MAX_SIZE);
}
};
public Integer get1(String s){
return lru_cache.get(s);
}
public void displayMap(){
/**
* Exception in thread "main" java.util.ConcurrentModificationException
at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(LinkedHashMap.java:373)
at java.util.LinkedHashMap$KeyIterator.next(LinkedHashMap.java:384)
at LRU.displayMap(LRU.java:23)
at LRU.main(LRU.java:47)
*/
*for(String key : lru_cache.keySet()){
System.out.println(lru_cache.get(key));
}*
// This parser works fine
// for(Map.Entry<String, Integer> kv : lru_cache.entrySet()){
// System.out.println(kv.getKey() + ":" + kv.getValue());
// }
}
public void set(String s, Integer val){
if(lru_cache.containsKey(s)){
lru_cache.put(s, get1(s) + val);
}
else{
lru_cache.put(s, val);
}
}
public static void main(String[] args) {
LRU lru = new LRU();
lru.set("Di", 1);
lru.set("Da", 1);
lru.set("Daa", 1);
lru.set("Di", 1);
lru.set("Di", 1);
lru.set("Daa", 2);
lru.set("Doo", 2);
lru.set("Doo", 1);
lru.set("Sa", 2);
lru.set("Na", 1);
lru.set("Di", 1);
lru.set("Daa", 1);
lru.displayMap();
}
}
回答by gparyani
Read the Javadoc for LinkedHashMap
:
A structural modification is any operation that adds or deletes one or more mappings or, in the case of access-ordered linked hash maps, affects iteration order. In insertion-ordered linked hash maps, merely changing the value associated with a key that is already contained in the map is not a structural modification. In access-ordered linked hash maps, merely querying the map with
get
is a structural modification.
结构修改是添加或删除一个或多个映射的任何操作,或者在访问顺序链接的散列映射的情况下,影响迭代顺序的任何操作。在插入顺序链接的哈希映射中,仅更改与映射中已包含的键关联的值不是结构修改。在按访问顺序链接的哈希映射中,仅查询映射
get
是一种结构修改。
Since you're passing in true
to the LinkedHashMap
constructor, it is in access order and when you are trying to get
something from it, you are structurally modifying it.
由于您正在传递true
给LinkedHashMap
构造函数,因此它按访问顺序排列,当您尝试从中获取get
某些内容时,您正在结构上修改它。
Also note that when you use the enhanced for
syntax, you are actually using an iterator. Simplified quote from JLS §14.14.2:
另请注意,当您使用增强for
语法时,您实际上是在使用迭代器。JLS §14.14.2 的简化引用:
The enhanced
for
statement has the form:EnhancedForStatement: for ( TargetType Identifier : Expression ) Statement
[...]
If the type of Expressionis a subtype of
Iterable<X>
for some type argumentX
, then letI
be the typejava.util.Iterator<X>
; otherwise, letI
be the raw typejava.util.Iterator
.The enhanced
for
statement is equivalent to a basicfor
statement of the form:for (I #i = Expression.iterator(); #i.hasNext(); ) { TargetType Identifier = (TargetType) #i.next(); Statement }
#i
is an automatically generated identifier that is distinct from any other identifiers (automatically generated or otherwise) that are in scope (§6.3) at the point where the enhanced for statement occurs.
增强
for
语句具有以下形式:EnhancedForStatement: for ( TargetType Identifier : Expression ) Statement
[...]
如果Expression的类型是
Iterable<X>
某个类型参数的子类型X
,则令I
为类型java.util.Iterator<X>
;否则,I
设为原始类型java.util.Iterator
。增强
for
语句等效于for
以下形式的基本语句:for (I #i = Expression.iterator(); #i.hasNext(); ) { TargetType Identifier = (TargetType) #i.next(); Statement }
#i
是一个自动生成的标识符,它不同于在增强的 for 语句发生点的范围内(第 6.3 节)的任何其他标识符(自动生成的或以其他方式生成的)。
Also, in the Javadoc for LinkedHashMap
:
此外,在 Javadoc 中LinkedHashMap
:
The iterators returned by the
iterator
method of the collections returned by all of this class's collection view methods are fail-fast: if the map is structurally modified at any time after the iterator is created, in any way except through the iterator's ownremove
method, the iterator will throw aConcurrentModificationException
.
由
iterator
该类的所有集合视图方法返回的集合的方法返回的迭代器是 快速失败的:如果在迭代器创建后的任何时间以任何方式修改映射的结构,除了通过迭代器自己的remove
方法,迭代器会抛出一个ConcurrentModificationException
.
Therefore, when you are calling get
on the map, you are performing structural modifications to it, causing the iterator in the enhanced-for to throw an exception. I think you meant to do this, which avoids calling get
:
因此,当您get
在地图上调用时,您正在对其进行结构修改,从而导致增强型 for 中的迭代器抛出异常。我认为您打算这样做,以避免调用get
:
for (Integer i : lru_cache.values()) {
System.out.println(i);
}
回答by Femi
You're using an access-ordered linked hash map: from the spec at http://docs.oracle.com/javase/7/docs/api/java/util/LinkedHashMap.html,
您正在使用按访问顺序排列的链接哈希映射:来自http://docs.oracle.com/javase/7/docs/api/java/util/LinkedHashMap.html的规范,
A structural modification is any operation that adds or deletes one or more mappings or, in the case of access-ordered linked hash maps, affects iteration order. In insertion-ordered linked hash maps, merely changing the value associated with a key that is already contained in the map is not a structural modification. In access-ordered linked hash maps, merely querying the map with get is a structural modification.)
结构修改是添加或删除一个或多个映射的任何操作,或者在访问顺序链接的散列映射的情况下,影响迭代顺序的任何操作。在插入顺序链接的哈希映射中,仅更改与映射中已包含的键关联的值不是结构修改。在按访问顺序链接的哈希映射中,仅使用 get 查询映射是一种结构修改。)
Simply calling get
is enough to be considered a structural modification, triggering the exception. If you use the entrySet()
sequence you're only querying the entry and NOT the map, so you don't trigger the ConcurrentModificationException
.
简单的调用get
就足以被认为是结构修改,触发异常。如果您使用entrySet()
序列,您只是查询条目而不是地图,因此您不会触发ConcurrentModificationException
.
回答by earcam
In the constructor of LinkedHashMap
you pass true
to get the LRU behaviour (meaning the eviction policy is access orderrather than false
for insertion order).
在构造函数中LinkedHashMap
传递true
,以获得LRU行为(指驱逐策略是访问顺序,而不是false
用于插入顺序)。
So every time you call get(key)
the underlying Map.Entry increments an access counter AND reorders the collection by moving the (last accessed) Map.Entry to the head of the list.
因此,每次调用get(key)
底层 Map.Entry 时,都会增加一个访问计数器,并通过将(上次访问的)Map.Entry 移动到列表的头部来重新排序集合。
The iterator (implicitly created by the for loop) checks the modified flag, which is different from the copy it took originally, so throws the ConcurrentModificationException
.
迭代器(由 for 循环隐式创建)检查修改后的标志,这与它最初采用的副本不同,因此抛出ConcurrentModificationException
.
To avoid this you should use the entrySet()
as the implementation is inherited from java.util.HashMap and therefore the iterator doesn't check the modification flags:
为了避免这种情况,您应该使用 ,entrySet()
因为实现是从 java.util.HashMap 继承的,因此迭代器不会检查修改标志:
for(Map.Entry<String,Integer> e : lru_cache.entrySet()){
System.out.println(e.getValue());
}
Be aware this class isn't threadsafe so in concurrent environments you will need to use an potentially expensive guards like Collections.synchronizedMap(Map). In this scenario a better option might be Google's Guava Cache.
请注意,此类不是线程安全的,因此在并发环境中,您需要使用可能昂贵的守卫,例如 Collections.synchronizedMap(Map)。在这种情况下,更好的选择可能是Google 的 Guava Cache。
回答by Sergey Alaev
Your code
你的代码
for(String key : lru_cache.keySet()){
System.out.println(lru_cache.get(key));
}
Actually compiles to:
实际上编译为:
Iterator<String> it = lru_cache.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
System.out.println(lru_cache.get(key));
}
Next, your LRU cache shrinks itself to MAX_SIZE elements not when calling set()
, but when calling get()
- above answers explain why.
接下来,您的 LRU 缓存不是在调用set()
时而是在调用时将自身缩小到 MAX_SIZE 元素get()
- 以上答案解释了原因。
Thus we have following behavior:
因此,我们有以下行为:
- new iterator created to iterate over
lru_cache.keySet()
collection lru_cache.get()
called to extract element from your cacheget()
invocation truncateslru_cache
to MAX_SIZE elements (in your case 3)- iterator
it
becomes invalid due to collection modification and throws on next iteration.
- 创建新的迭代器以迭代
lru_cache.keySet()
集合 lru_cache.get()
调用从缓存中提取元素get()
调用截断lru_cache
为 MAX_SIZE 个元素(在您的情况下为 3)- 迭代器
it
由于集合修改而无效并在下一次迭代中抛出。
回答by SparkOn
java.util.ConcurrentModificationException
: If there are any structural changes (additions, removals, rehashing, etc.) to the underlying list while the iterator exists. The iterator checks to see if the list has changed before each operation. This is known as 'failsafe operation'.
java.util.ConcurrentModificationException
: 如果迭代器存在时底层列表有任何结构变化(添加、删除、重新散列等)。迭代器会在每次操作之前检查列表是否已更改。这称为“故障安全操作”。
If a thread modifies a collection directly while it is iterating over the collection with a fail-fast iterator, the iterator will throw this exception.Here you cannot call the get()
method while using an iterator because calling get()
structurally modifies the map and hence the next call to one of the iterators method fails and throws a ConcurrentModificationException
.
如果线程在使用快速失败迭代器迭代集合时直接修改集合,则迭代器将抛出此异常。此处您不能get()
在使用迭代器时调用该方法,因为调用get()
会在结构上修改映射,因此下一次调用其中一个迭代器方法失败并抛出一个ConcurrentModificationException
.
回答by sampopes
It is coz of fail-fast behaviour of collections framework also when you modify the list (by adding or removing elements) while traversing a list with this error will be there with Iterator. I came across this error some time back . Refer below threads to for detail info.
当您在遍历列表时修改列表(通过添加或删除元素)时,集合框架的快速失败行为也是因为迭代器会出现此错误。前段时间我遇到了这个错误。有关详细信息,请参阅以下线程。
ConcurrentModificationException when adding inside a foreach loop in ArrayList
在 ArrayList 的 foreach 循环中添加时出现 ConcurrentModificationException
Though this says array list, it applies for most of the collection(s) data strucutres.
虽然这说的是数组列表,但它适用于大多数集合数据结构。
Concurrent Modification Exception : adding to an ArrayList
http://docs.oracle.com/javase/6/docs/api/java/util/ConcurrentModificationException.html
http://docs.oracle.com/javase/6/docs/api/java/util/ConcurrentModificationException.html