我正在尝试了解故障安全迭代器的工作原理。我在并发hashmap中遇到了一些奇怪的东西。代码如下 -
ConcurrentHashMap<String,String> hm = new ConcurrentHashMap<String,String>();
hm.put("name1", "value1");
hm.put("name2", "value2");
hm.put("name3", "value3");
Iterator<String> itr = hm.keySet().iterator();
int index = 0;
while(itr.hasNext()) {
System.out.println(hm.get(itr.next()));
hm.put(index+"1", index+"2");
hm.remove("name2");
index++;
}
System.out.println("--- Second iteration --- ");
Iterator<String> itr2 = hm.keySet().iterator();
while(itr2.hasNext()) {
System.out.println(hm.get(itr2.next()));
}
打印:
value3
null
value1
--- Second iteration ---
12
02
value3
value1
22
我很困惑为什么在第一种情况下删除元素时更新而添加不是!我正在使用1.8运行时环境。
答案 0 :(得分:3)
这是因为ConcurrentHashMap
的迭代器在编辑地图时不会抛出ConcurrentModificationException
。
此映射不会抛出此异常,而是返回反映自创建迭代器以来某些时间段内存在的键/值以及当前时间。这也在javadoc:
中说明检索操作(包括get)一般不会阻塞,因此可能与更新操作重叠(包括put和remove)。检索反映了最近完成的更新操作的结果。对于诸如putAll和clear之类的聚合操作,并发检索可能反映仅插入或删除某些条目。类似地,Iterators和Enumerations在迭代器/枚举的创建时或之后的某个时刻返回反映哈希表状态的元素。它们不会抛出ConcurrentModificationException。但是,迭代器被设计为一次只能由一个线程使用。
如果你需要同时使用值和键,而不会导致这样的错误,你应该遍历地图的入口集而不是键集,这可以通过以下方式完成:
Iterator<Map.Entry<String,String>> itr = hm.entrySet().iterator();
int index = 0;
while(itr.hasNext()) {
Map.Entry<String,String> next = itr.next();
System.out.println(next.getValue());
hm.put(index+"1", index+"2");
hm.remove("name2");
index++;
}
答案 1 :(得分:2)
ConcurrentHashMap的Iterator以线程安全的方式导航底层数据结构。它是一次性传递,如果它已经传递了一个键/条目&#34;它不会看到这种变化。如果更改发生在迭代器已到达的点之外,则可能会看到更改,如果它在到达之前没有再次更改。这是一个例子;
Map<Integer, Boolean> map = new ConcurrentHashMap<>();
for (int i = 0; i < 10; i++)
map.put(i, true);
System.out.println(map.keySet());
List<Integer> ints = new ArrayList<>(map.keySet());
Map<Integer, Boolean> map2 = new ConcurrentHashMap<>();
for (int i = 0; i < 10; i += 2)
map2.put(ints.get(i), false);
System.out.println("All evens " + map2.keySet());
// all evens
Iterator<Integer> iter = map2.keySet().iterator();
for (int i = 8; i >= 0; i -= 2) {
// remove evens and add odds
map2.remove(ints.get(8 - i));
map2.remove(ints.get(i));
map2.put(ints.get(i + 1), false);
System.out.println(iter.next() +" - full set is: "+map2.keySet());
}
while (iter.hasNext())
System.out.println(iter.next());
System.out.println("All odds " + map2.keySet());
打印
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
All evens [0, 2, 4, 6, 8]
0 - full set is: [2, 4, 6, 9]
2 - full set is: [4, 7, 9]
4 - full set is: [5, 7, 9]
5 - full set is: [3, 5, 7, 9]
7 - full set is: [1, 3, 5, 7, 9]
9
All odds [1, 3, 5, 7, 9]
注意迭代器看到的键如果不是在任何给定时刻设置的键。相反,在(在这种情况下更高的数字)之后发生的变化是可见的,但是过去的键(在这种情况下是较低的数字)不是。