Почему ArrayList не генерирует ConcurrentModificationException при изменении из нескольких потоков?

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

Выше приведено определение ConcurrentModificationException из javadoc.

Поэтому я пытаюсь проверить код ниже:

final List tickets = new ArrayList(100000);
for (int i = 0; i < 100000; i++) {
    tickets.add("ticket NO," + i);
}
for (int i = 0; i < 10; i++) {
    Thread salethread = new Thread() {
        public void run() {
            while (tickets.size() > 0) {
                tickets.remove(0);
                System.out.println(Thread.currentThread().getId()+"Remove 0");
            }
        }
    };
    salethread.start();
}

Код прост. 10 потоков удаляют элемент из объекта arraylist. Уверен, что несколько потоков обращаются к одному объекту. Но это работает хорошо. Не исключение не выбрасывается. Почему я'

Ответы на вопрос(5)

ConcurrentModificationException быть брошенным

призваниеremove(0) просто удалит первый элемент. Это может быть нетак же элемент, предназначенный вызывающей стороной, если другой поток удаляет 0 до завершения выполнения.

Решение Вопроса

ArrayList Javadoc в вашу пользу. Соответствующие части, которые объясняют поведение, которое вы видите, выделены.

Обратите внимание, что эта реализация не синхронизирована. Если несколько потоков обращаются к экземпляру ArrayList одновременно, и хотя бы один из потоков структурно изменяет список, он должен быть синхронизирован извне, (Структурная модификация - это любая операция, которая добавляет или удаляет один или несколько элементов или явно изменяет размер базового массива; просто установка значения элемента не является структурной модификацией.) Обычно это выполняется путем синхронизации с некоторым объектом, который естественным образом инкапсулирует список. Если такого объекта не существует, список должен быть "завернутые» используя метод Collections.synchronizedList. Это лучше всего делать во время создания, чтобы предотвратить случайный несинхронизированный доступ к списку:

Список список = Collections.synchronizedList (новый ArrayList (...));

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

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

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

Если это щекочет ваше воображение, вы также можете просмотреть реализациюArrayList.remove, чтобы получить лучшее понимание того, как это работает.

Но это работает хорошо. Не исключение не выбрасывается. Зачем?

Просто потому, что это одновременное изменение допустимо.

Описание исключения говорит это:

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

Явный смысл в том, что есть (или может быть)допустимый одновременные модификации. И фактически для стандартных классов одновременных коллекций Java допускаются одновременные модификации ... при условии, что они нене может произойти во время итерации.

Причина этого заключается в том, что для неконкурентных коллекций модификация во время итерации принципиально небезопасна и непредсказуема. Даже если вы должны были синхронизировать правильно (и это неt easy1), результат все равно будет непредсказуемым. "отказоустойчивость быстро» проверки на одновременные модификации были включены в обычные классы коллекций, потому что это был общий источник Heisenbugs в многопоточных приложениях, которые использовали классы коллекций Java 1.1.

1- Например,synchronizedXxx» классы обертки нет, и могусинхронизировать с итераторами. Проблема в том, что итерация включает в себя чередующиеся вызовыnext() а такжеhasNext()и единственный способ выполнить пару вызовов методов при исключении других потоков - это использовать внешнюю синхронизацию. Подход обертки непрактично в Java.

Я нея думаюодновременно» означает связанный с потоком в этом случае, или, по крайней мере, это неЭто обязательно означает.ConcurrentModificationExceptionОбычно возникают из-за изменения коллекции во время итерации по ней.

List list = new ArrayList();
for(String s : list)
{
     //modifying list results in ConcurrentModificationException
     list.add("don't do this");     

}

Обратите внимание, чтоIterator В классе есть несколько методов, которые могут обойти это:

for(Iterator it = list.iterator(); it.hasNext())
{
     //no ConcurrentModificationException
     it.remove(); 
}
 John Vint19 февр. 2013 г., 03:58
+1 Коротко и просто. Параллельный не обязательно означает параллельный (как показывает ваш ответ).

Причина, по которой вы не получаетеConcurrentModificationException в том, чтоArrayList.remove не бросает один. Вы, вероятно, можете получить его, запустив дополнительный поток, который проходит через массив:

final List tickets = new ArrayList(100000);
for (int i = 0; i < 100000; i++) {
    tickets.add("ticket NO," + i);
}
for (int i = 0; i < 10; i++) {
    Thread salethread = new Thread() {
        public void run() {
            while (tickets.size() > 0) {
                tickets.remove(0);
                System.out.println(Thread.currentThread().getId()+"Remove 0");
            }
        }
    };
    salethread.start();
}
new Thread() {
    public void run() {
        int totalLength = 0;
        for (String s : tickets) {
            totalLength += s.length();
        }
    }
}.start();

Ваш ответ на вопрос