Почему CopyOnWriteArrayList нужны копии для операций записи и чтения?

Исходя из этой статьи, говорится:

Когда мы используем любой из методов изменения, например add () или remove (), все содержимое CopyOnWriteArrayList копируется в новую внутреннюю копию.

Благодаря этому простому факту мы можем безопасно перебирать список даже при одновременном изменении.

Когда мы вызываем метод iterator () для CopyOnWriteArrayList, мы получаем обратно Iterator, зарезервированный неизменным моментальным снимком содержимого CopyOnWriteArrayList.

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

Следующий простой вопрос: почему и то, и другое? В основном, насколько я понимаю, операции записи выполняются с новой копией, а операция чтения - с клоном коллекции.

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

Я надеюсь, что этот вопрос будет законным, поскольку я буквально проверил все возможные источники в Интернете, и ни одна статья не помогла мне устранить эту путаницу. Что мне здесь не хватает?


person Stefan    schedule 05.04.2021    source источник


Ответы (1)


CopyOnWriteArrayList не создает копию массива при вызове iterator, поскольку документы говорит:

Метод итератора в стиле моментального снимка использует ссылку на состояние массива в момент создания итератора.

Обратите внимание на слово «ссылка».

Это предложение сформулировано довольно неудачно:

Его содержимое является точной копией данных, находящихся внутри ArrayList с момента создания Iterator.

Это не означает, что копия массива создается при вызове iterator(). Надо было сказать:

Его содержимое такое же, как данные, находящиеся внутри ArrayList с момента создания Iterator.

Более важный момент этого абзаца:

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

Это означает, что если вы создаете итератор, а затем продолжаете каким-то образом изменять список, итератор не увидит этих изменений. Почему? Поскольку мутации выполняются путем создания нового массива, который имеет мутации, но итератор выполняет итерацию по старому массиву, в котором нет мутаций. Вот почему мы говорим, что итератор делает снимок.

Вот код из OpenJDK для иллюстрации.

В _ 4_, он просто создает COWIterator с _ 6_, который получает снимок, возвращая изменчивое поле array:

final Object[] getArray() {
    return array;
}

...

public Iterator<E> iterator() {
    return new COWIterator<E>(getArray(), 0);
}

И методы-мутаторы, такие как _ 9_ устанавливает поле array:

final void setArray(Object[] a) {
    array = a;
}

...

public boolean add(E e) {
    Object[] elements = getArray();
    int len = elements.length;
    Object[] newElements = Arrays.copyOf(elements, len + 1);
    newElements[len] = e;
    setArray(newElements);
    return true;
}

Я удалил код блокировки (разблокировки), чтобы было легче увидеть, что происходит.

person Sweeper    schedule 05.04.2021
comment
Спасибо! Но тогда почему обычно говорят, что fail-safe iterators traverse over the clone of the collection -example - это CopyOnWriteArrayList. Clone = Copy, верно? А вы только что сказали, что копии нет вообще. Извините, я все еще запутался :) - person Stefan; 05.04.2021
comment
@Stefan Кто это вообще сказал? Я даже не могу найти слово «отказоустойчивый» в статье, на которую вы ссылаетесь. - person Sweeper; 05.04.2021
comment
Вот, например (прокрутите немного вниз, пока не найдете Отказоустойчивое внутреннее функционирование): anmolsehgal.medium.com/. И не было бы проще для CopyOnWriteArrayList читать из моментального снимка и позволять изменять операции над исходным массивом? Технически он по-прежнему должен работать правильно, так как моментальный снимок не обнаруживает никаких мутаций. - person Stefan; 05.04.2021
comment
@Stefan Они либо (ошибочно) думают, что getArray фактически создает копию, либо имеют в виду, что создается копия ссылки на массив, что правильно. Я не являюсь автором этой статьи, поэтому не могу сказать, что они действительно хотели сказать. - person Sweeper; 05.04.2021
comment
@Stefan Это больше не будет копией при записи, не так ли? В любом случае обратная сторона этого заключается в том, что вы получите больше блокировок. Вам также нужно будет заблокировать геттеры, иначе они могут увидеть некоторое промежуточное (недопустимое) состояние исходного массива при его мутации. - person Sweeper; 05.04.2021