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

    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
Jay 25 Фев 2016 в 11:11

2 ответа

Лучший ответ

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

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]

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

2
Peter Lawrey 25 Фев 2016 в 09:09

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

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

Операции получения (включая получение) обычно не блокируются, поэтому могут перекрываться с операциями обновления (включая размещение и удаление). Извлечения отражают результаты последних завершенных операций обновления, сохраняемых с момента их начала. Для агрегированных операций, таких как putAll и clear, одновременные извлечения могут отражать вставку или удаление только некоторых записей. Точно так же итераторы и перечисления возвращают элементы, отражающие состояние хеш-таблицы в какой-то момент во время или после создания итератора / перечисления. Они не генерируют исключение 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++;
}
3
Ferrybig 25 Фев 2016 в 08:34