Подтвердить что ты не робот

Почему этот код не генерирует исключение ConcurrentModificationException?

Почему этот код не бросает ConcurrentModificationException? Он изменяет Collection во время итерации через него, не используя метод Iterator.remove(), который должен быть единственным безопасным способом удаления.

List<String> strings = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String string : strings)
    if ("B".equals(string))
        strings.remove("B");
System.out.println(strings);

Я получаю тот же результат, если заменить ArrayList на LinkedList. Однако, если я изменил список на ("A", "B", "C", "D) или просто ("A", "B"), я получаю исключение, как ожидалось. Что происходит? Я использую jdk1.8.0_25, если это имеет значение.

EDIT

Я нашел следующую ссылку

http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4902078

Соответствующий раздел

Наивное решение состоит в том, чтобы добавить проверки на comodification в hasNext в AbstractList, но это удваивает стоимость проверки кодофикации. Оказывается, достаточно выполнить тест только на последнем итерации, что практически ничего не добавляет к стоимости. Другими словами, текущая реализация hasNext:

    public boolean hasNext() {
        return nextIndex() < size;
    }

Заменяется этой реализацией:

    public boolean hasNext() {
        if (cursor != size())
            return true;
        checkForComodification();
        return false;
    }

Это изменение не будет сделано, потому что внутренний регулирующий орган ВС отклонил его. Официальное постановление указало, что изменение "имеет продемонстрировали потенциал значительного воздействия на совместимость по существующему коду". ( "Эффект совместимости" заключается в том, что исправление имеет возможность заменить молчаливое неправильное поведение ConcurrentModificationException.)

4b9b3361

Ответ 1

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

Этот сценарий не бросает ConcurrentModificationException, потому что next() не вызывается на созданный итератор после модификации.

Для каждого цикла на самом деле итераторы, поэтому ваш код выглядит примерно так:

List<String> strings = new ArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> iter = strings.iterator();
while(iter.hasNext()){
    String string = iter.next();
    if ("B".equals(string))
        strings.remove("B");
}
System.out.println(strings);

Рассмотрите свой код, запущенный в указанном вами списке. Итерации выглядят так:

  • hasNext() возвращает true, enter loop, → iter переходит к индексу 0, string = "A", не удаляется
  • hasNext() возвращает true, цикл продолжения → iter переходит к индексу 1, строка = "B", удаляется. strings теперь имеет длину 2.
  • hasNext() возвращает false (iter в настоящее время находится в последнем индексе, больше не нужно индексов), цикл выхода.

Таким образом, при отправке ConcurrentModificationException, когда вызов next() обнаруживает, что была сделана модификация, этот сценарий позволяет избегать такого исключения.

Для двух других результатов мы получаем исключения. Для "A", "B", "C", "D" после удаления "B" мы все еще находимся в цикле, а next() обнаруживает ConcurrentModificationException, тогда как для "A", "B" я бы предположил, что это какой-то ArrayIndexOutOfBounds, который был пойман и повторно брошен как ConcurrentModificationException

Ответ 2

hasNext в итераторе ArrayList просто

public boolean hasNext() {
    return cursor != size;
}

После вызова remove итератор имеет индекс 2, а размер списка равен 2, поэтому он сообщает, что итерация завершена. Нет одновременной проверки модификации. С ( "A", "B", "C", "D" или ( "A", "B" ) итератор не находится в новом конце списка, поэтому вызывается next, и это бросает исключение.

ConcurrentModificationException - это только отладочная помощь. Вы не можете полагаться на них.

Ответ 3

@Тавиан Барнс в точности прав. Это исключение не может быть гарантировано выдано, если параллельная модификация, о которой идет речь, несинхронизирована. Цитирование из спецификации java.util.ConcurrentModification:

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

Ссылка на JavaDoc для ConcurrentModificationException