«1. Введение

В этой статье мы познакомимся с концепцией отказоустойчивых и отказоустойчивых итераторов.

Системы 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 по мере возможности.

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

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

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 каждой такой коллекции.

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

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

Итераторы коллекций из пакета 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);
}

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

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

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

4. Заключение

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

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