如果我删除下一个对象,ConcurrentHashMap返回null

时间:2016-02-25 08:11:04

标签: java concurrenthashmap

我正在尝试了解故障安全迭代器的工作原理。我在并发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运行时环境。

2 个答案:

答案 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]

注意迭代器看到的键如果不是在任何给定时刻设置的键。相反,在(在这种情况下更高的数字)之后发生的变化是可见的,但是过去的键(在这种情况下是较低的数字)不是。