2016-02-25 6 views
2

Я пытаюсь понять, как работает отказоустойчивый итератор. Я столкнулся с чем-то странным в параллельном хэшмапе. Код выглядит следующим образом -ConcurrentHashMap возвращает null, если я удаляю следующий объект

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.

+1

Нет гарантии, что вы будете или не увидите изменений, которые вы выполняете в процессе Итерации. Видите ли вы, это будет зависеть от того, как далеко вы прошли и где ключ будет помещен во внутреннюю структуру данных. –

ответ

2

Итератор для ConcurrentHashMap осуществляет навигацию по базовой структуре данных таким образом, который является потокобезопасным. Он делает это как один проход, и если ключ/элемент, который он «прошел», он не увидит это изменение. Если изменение происходит за пределами точки, к которой Итератор достиг, он может увидеть это изменение, если оно не изменится снова, прежде чем оно туда попадет. Вот пример;

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] 

Обратите внимание, как ключи итератор видит если не ключи, установленные в данный момент. Вместо этого видны изменения, которые происходят после (в данном случае более высоких чисел), но ключи, которые находятся в прошлом (в данном случае более низкие номера), не являются.

+0

Невероятный пример! Большое спасибо. – Jay

3

Это потому, что итераторы ConcurrentHashMap не меняют ConcurrentModificationException при редактировании Карты.

Вместо того, чтобы выбрасывать это исключение, эта карта будет возвращать ключи/значения, отражающие то, что существовало в некоторый промежуток времени с момента создания итератора, и текущее время. Это также указано в javadoc:

операция выборки (включая get-), как правило, не блокирует, поэтому может перекрываться с операциями обновления (в том числе поставить и удалить). Retrievals отражают результаты последних завершенных операций по обновлению с их началом. Для совокупных операций, таких как putAll и clear, одновременное извлечение может отражать вставку или удаление только некоторых записей. Аналогично, итераторы и перечисления возвращают элементы, отражающие состояние хеш-таблицы в какой-то момент времени или после создания итератора/перечисления. Они не выбрасывают ConcurrentModificationException. Однако итераторы предназначены для использования только по одному потоку за раз.

Если вам нужно использовать значение и ключи в то же время, не вызывая ошибки, как это, вы должны перебираем entryset карты содержат в вместо этого набора ключей, это может быть сделано следующим:

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++; 
} 
+0

Нет гарантии, что снимок сделан (в отличие от CopyOnWriteArrayList/Set). Любое изменение, которое происходит при многократном или многократном отображении. –

+0

@PeterLawrey Вы уверены? Он говорит это в своем javadoc: «Аналогично, итераторы и перечисления возвращают элементы **, отражающие состояние хеш-таблицы в какой-то момент времени или после создания ** итератора/перечисления» – Ferrybig

+0

Итератор не берет копию коллекция и не является атомарной (так что для больших коллекций нет производительности, и нет риска утечки памяти), это означает, что при повторении вы можете увидеть набор ключей, например, которые карта никогда не имела в любой момент , –