当我在下面的代码中迭代java.util.ConcurrentModificationException
结构时,不确定触发LinkedHashMap
的是什么。使用Map.Entry
方法可以正常工作。没有从之前的帖子中得到关于触发这个问题的一个很好的解释。
任何帮助都将不胜感激。
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();
}
}
答案 0 :(得分:41)
结构修改是添加或删除一个或多个的任何操作 更多映射,或者在访问顺序链接哈希映射的情况下, 影响迭代顺序。仅在插入顺序链接哈希映射中 更改与已包含的密钥关联的值 地图不是结构修改。 在访问顺序链接 哈希映射,仅用
get
查询地图是一种结构 修改强>
由于您将true
传递给LinkedHashMap
构造函数,因此它处于访问顺序,当您尝试从get
构建某些内容时,您正在对其进行结构修改。< / p>
另请注意,使用增强的for
语法时,实际上是在使用迭代器。来自JLS §14.14.2的简化报价:
增强型
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
是一个自动生成的标识符,不同于其中的任何其他标识符(自动生成或以其他方式) 范围(第6.3节)在增强的声明发生时。
另外,在LinkedHashMap
的Javadoc中:
集合的
iterator
方法返回的迭代器 所有这个类的集合视图方法都返回了 fail-fast :如果在创建迭代器之后的任何时候对地图进行结构修改,除了通过迭代器自己以外的任何方式remove
方法,迭代器会抛出一个ConcurrentModificationException
。
因此,当您在地图上调用get
时,您正在对它进行结构修改,导致enhanced-for中的迭代器抛出异常。我认为你打算这样做,这可以避免调用get
:
for (Integer i : lru_cache.values()) {
System.out.println(i);
}
答案 1 :(得分:8)
您正在使用访问顺序链接哈希映射:来自http://docs.oracle.com/javase/7/docs/api/java/util/LinkedHashMap.html的规范,
结构修改是添加或删除一个或多个的任何操作 更多映射,或者在访问顺序链接哈希映射的情况下, 影响迭代顺序。仅在插入顺序链接哈希映射中 更改与已包含的密钥关联的值 地图不是结构修改。在访问有序链接 哈希映射,只是用get查询地图是一种结构 修改。)
简单地调用get
就足以被视为结构修改,触发异常。如果您使用entrySet()
序列,则只查询条目而不是地图,因此您不会触发ConcurrentModificationException
。
答案 2 :(得分:4)
在LinkedHashMap
的构造函数中,您传递true
以获取LRU行为(意味着驱逐策略为访问顺序而非<{1}} 广告订单)。
因此,每次调用false
时,底层Map.Entry会增加一个访问计数器,并通过将(最后访问的)Map.Entry移动到列表的头部来重新排序集合。
迭代器(由for循环隐式创建)检查修改后的标志,该标志与最初使用的副本不同,因此抛出get(key)
。
为了避免这种情况,您应该使用ConcurrentModificationException
,因为实现是从java.util.HashMap继承的,因此迭代器不检查修改标志:
entrySet()
请注意,此类不是线程安全的,因此在并发环境中,您需要使用像Collections.synchronizedMap(Map)这样的潜在昂贵的防护。在这种情况下,更好的选择可能是Google's Guava Cache。
答案 3 :(得分:2)
java.util.ConcurrentModificationException
:如果迭代器存在,则对基础列表进行任何结构更改(添加,删除,重新散列等)。迭代器在每次操作之前检查列表是否已更改。这被称为“故障安全操作”。
如果线程在使用失败快速迭代器迭代集合时直接修改集合,则迭代器将抛出此异常。在使用迭代器时,您无法调用get()
方法,因为调用{{ 1}}从结构上修改了映射,因此对其中一个迭代器方法的下一次调用失败并抛出get()
。
答案 4 :(得分:1)
您的代码
for(String key : lru_cache.keySet()){
System.out.println(lru_cache.get(key));
}
实际上编译为:
Iterator<String> it = lru_cache.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
System.out.println(lru_cache.get(key));
}
接下来,您的LRU缓存会在调用set()
时将自身缩小为MAX_SIZE元素,但在调用get()
时 - 上述答案解释原因。
因此我们有以下行为:
lru_cache.keySet()
集合lru_cache.get()
被调用以从缓存中提取元素get()
调用将lru_cache
截断为MAX_SIZE个元素(在您的情况下为3)it
由于集合修改而变为无效,并在下一次迭代时抛出。答案 5 :(得分:1)
当您修改列表(通过添加或删除元素)时,它也是集合框架的快速失败行为,而遍历具有此错误的列表将与Iterator一起使用。一段时间后我偶然发现了这个错误。有关详细信息,请参阅以下主题。
ConcurrentModificationException when adding inside a foreach loop in ArrayList
虽然这表示数组列表,但它适用于大多数集合数据结构。
Concurrent Modification Exception : adding to an ArrayList
http://docs.oracle.com/javase/6/docs/api/java/util/ConcurrentModificationException.html