Перейти к основному содержимому

Отказоустойчивый итератор против отказоустойчивого итератора

· 3 мин. чтения

1. Введение

В этой статье мы представим концепцию Fail-Fast и Fail-Safe Iterators .

Системы Fail-Fast прерывают работу как можно быстрее, немедленно обнаруживая сбои и останавливая всю операцию.

Принимая во внимание, что отказоустойчивые системы не прерывают операцию в случае сбоя. Такие системы стараются максимально избегать возникновения сбоев.

2. Отказоустойчивые итераторы

Отказоустойчивые итераторы в Java не работают, когда базовая коллекция изменяется.

Коллекции поддерживают внутренний счетчик под названием modCount . Каждый раз, когда элемент добавляется или удаляется из коллекции , этот счетчик увеличивается.

При повторении при каждом вызове next() текущее значение modCount сравнивается с начальным значением. В случае несоответствия генерируется исключение ConcurrentModificationException , которое прерывает всю операцию.

Итераторы по умолчанию для коллекций из пакета java.util, таких как ArrayList , HashMap и т. д ., являются Fail-Fast.

ArrayList<Integer> numbers = // ...

Iterator<Integer> iterator = numbers.iterator();
while (iterator.hasNext()) {
Integer number = iterator.next();
numbers.add(50);
}

В приведенном выше фрагменте кода исключение ConcurrentModificationException возникает в начале следующего цикла итерации после выполнения модификации.

Поведение Fail-Fast не гарантируется во всех сценариях, поскольку невозможно предсказать поведение в случае одновременных изменений. Эти итераторы генерируют исключение ConcurrentModificationException по мере возможности .

Если во время итерации над Collection элемент удаляется с помощью метода iterator remove() , это совершенно безопасно и не вызывает исключения .

Однако, если для удаления элемента используется метод remove() объекта Collection , возникает исключение: ``

ArrayList<Integer> numbers = // ...

Iterator<Integer> iterator = numbers.iterator();
while (iterator.hasNext()) {
if (iterator.next() == 30) {
iterator.remove(); // ok!
}
}

iterator = numbers.iterator();
while (iterator.hasNext()) {
if (iterator.next() == 40) {
numbers.remove(2); // exception
}
}

3. Отказоустойчивые итераторы

Отказоустойчивые итераторы предпочитают отсутствие сбоев неудобствам обработки исключений.

Эти итераторы создают клон фактической коллекции и перебирают ее. Если какое-либо изменение произойдет после создания итератора, копия останется нетронутой. Следовательно, эти итераторы продолжают перебирать коллекцию , даже если она изменена.

Однако важно помнить, что действительно отказоустойчивых итераторов не существует. Правильный термин — слабо согласованный.

Это означает, что если коллекция изменяется во время итерации, то, что видит итератор , слабо гарантировано . Это поведение может быть разным для разных коллекций и задокументировано в Javadocs каждой такой коллекции .

Однако отказоустойчивые итераторы имеют несколько недостатков. Одним из недостатков является то, что Iterator не гарантирует возврат обновленных данных из Collection , так как он работает с клоном, а не с фактическим Collection .

Другим недостатком являются накладные расходы на создание копии Collection как по времени, так и по памяти.

Итераторы для коллекций из пакета java.util.concurrent , такие как ConcurrentHashMap , CopyOnWriteArrayList и т. д., по своей природе являются отказоустойчивыми.

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

map.put("First", 10);
map.put("Second", 20);
map.put("Third", 30);
map.put("Fourth", 40);

Iterator<String> iterator = map.keySet().iterator();

while (iterator.hasNext()) {
String key = iterator.next();
map.put("Fifth", 50);
}

В приведенном выше фрагменте кода мы используем Fail-Safe Iterator . Следовательно, несмотря на то, что новый элемент добавляется в коллекцию во время итерации, он не генерирует исключение.

Итератор по умолчанию `для ConcurrentHashMap слабо согласован. Это означает, что этот итератор может допускать одновременную модификацию, перемещаться по элементам в том виде, в каком они существовали на момент создания Iterator , и может (но не обязательно) отражать изменения в Collection после создания Iterator` .

Следовательно, в приведенном выше фрагменте кода итерация повторяется пять раз, что означает обнаружение нового добавленного элемента в Collection .

4. Вывод

В этом руководстве мы увидели, что означают отказоустойчивые и отказоустойчивые итераторы и как они реализованы в Java.

Полный код, представленный в этой статье, доступен на GitHub .