Изменить список во время итерации

Пока я тестировал свой собственный ответ, чтобы получить выходные данные для этого вопроса, я получил следующие выходные данные для данного содержимого списка:

// 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 следует использовать только для обнаружения ошибок.

Другие вопросы по тегам