Изменить список во время итерации
Пока я тестировал свой собственный ответ, чтобы получить выходные данные для этого вопроса, я получил следующие выходные данные для данного содержимого списка:
// Add some strings into the list
list.add("Item 1");
list.add("Item 2");
list.add("Item 3");
list.add("Item 4");
Выход:
Item 1
Item 2
Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at com.akefirad.tests.Main$1.run(Main.java:34)
at java.lang.Thread.run(Thread.java:745)
Но если использовать следующий список:
// Add some strings into the list
list.add("Item 1");
list.add("Item 2");
list.add("Item 3");
Выход:
Item 1
Item 2
В выводе нет исключений, и будут напечатаны только два первых элемента. Кто-нибудь может объяснить, почему он так себя ведет? Спасибо
Примечание: код здесь.
РЕДАКТИРОВАНИЕ: У меня вопрос, почему у меня не напечатан третий элемент (что означает, что список изменен), и пока нет исключений.
РЕДАКТИРОВАТЬ код для создания исключения, обратите внимание на содержание списка:
public class Main {
public static void main(String[] args) throws InterruptedException
{
final ArrayList<String> list = new ArrayList<String>();
list.add("Item 1");
list.add("Item 2");
list.add("Item 3");
list.add("Item 4");
Thread thread = new Thread(new Runnable()
{
@Override
public void run ()
{
for (String s : list)
{
System.out.println(s);
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
});
thread.start();
Thread.sleep(2000);
list.remove(0);
}
}
2 ответа
По сути, вы изменяете список в одном потоке, а перебираете его в другом, и используемая вами реализация списка не поддерживает это.
ArrayList
Реализация итератора, по-видимому, обнаруживает только недопустимые модификации при обращении к next()
не по вызову hasNext()
, Так что, если вы попадете в последнюю итерацию цикла перед remove()
позвоните, тогда вы не получите исключение - hasNext()
просто вернусь false
, С другой стороны, если remove()
происходит до последнего звонка next()
(и если это замечено в другом потоке - здесь задействована модель памяти), вы получите исключение. Так, например, если вы измените свой сон в цикле на Thread.sleep(2500)
тогда вы получите исключение в начале второй итерации, потому что remove()
вызов произойдет до этого.
Если вы хотите использовать список в нескольких потоках, и хотя бы один из них изменяет его, вам следует использовать реализацию, которая поддерживает это, например, CopyOnWriteArrayList
,
Поведение, которое вы пытаетесь воспроизвести, сильно зависит от времени.
Вы получаете исключение, если и только если два потока перекрываются во времени при изменении списка.
В противном случае вы не получите исключение.
С помощью Thread.sleep()
не может надежно вызвать перекрытие между двумя потоками, потому что ядро всегда может принять решение о произвольном планировании потоков после их пробуждения.
Обновление: ОП хочет знать, должен ли происходить ровно один из следующих двух случаев:
- Все три элемента напечатаны
- Некоторые из них напечатаны и исключение
Ответ Джона Скита указывает на случай, когда напечатано менее трех элементов без исключения, что означает, что ответ отрицательный.
В более общем плане, ищет ConcurrentModificationException
не является надежным способом обнаружения нескольких потоков, изменяющих и считывающих объект одновременно. Фактически, Javadoc для Исключения обращается к этому пункту очень определенно.
Обратите внимание, что отказоустойчивое поведение не может быть гарантировано, так как, вообще говоря, невозможно сделать какие-либо жесткие гарантии при наличии несинхронизированной параллельной модификации. Отказоустойчивые операции создают исключительную ситуацию ConcurrentModificationException. Поэтому было бы неправильно писать программу, которая зависела от этого исключения для его корректности: исключение ConcurrentModificationException следует использовать только для обнаружения ошибок.