Я смотрел в исходный код OpenJDK CopyOnWriteArrayList
и кажется, что все операции записи защищены той же блокировкой, и операции чтения не защищены вообще. Как я понимаю под JMM, все доступы к переменной (и чтение и запись) должны быть защищены блокировкой, или эффекты переупорядочения могут произойти.
Например, set(int, E)
метод содержит эти строки (под блокировкой):
/* 1 */ int len = elements.length;
/* 2 */ Object[] newElements = Arrays.copyOf(elements, len);
/* 3 */ newElements[index] = element;
/* 4 */ setArray(newElements);
get(int)
метод, с другой стороны, только делает return get(getArray(), index);
.
В моем понимании JMM это означает это get
может наблюдать массив в непоследовательном состоянии, если операторы 1-4 переупорядочиваются как 1-2 (новых)-4-2 (copyOf)-3.
Сделайте я понимаю JMM неправильно или есть ли любые другие объяснения на почему CopyOnWriteArrayList
ориентировано на многопотоковое исполнение?
Если вы посмотрите на ссылку на базовый массив, вы увидите, что он помечен как volatile
. Когда происходит операция записи (например, в приведенном выше фрагменте), эта энергозависимая
ссылка обновляется только в последнем операторе через setArray
. До этого момента любые операции чтения будут возвращать элементы из старой копии массива.
Важным моментом является то, что обновление массива является атомарной операцией , и поэтому при чтении всегда будет отображаться массив в согласованном состоянии.
Преимущество снятия блокировки только для операций записи заключается в улучшенной пропускной способности для операций чтения: это связано с тем, что операции записи для CopyOnWriteArrayList
потенциально могут быть очень медленными, поскольку они включают копирование всего списка.
Получение ссылки на массив - это атомарная операция. Таким образом, читатели увидят либо старый массив, либо новый массив - в любом случае состояние будет согласованным. ( set (int, E)
вычисляет новое содержимое массива перед установкой ссылки, поэтому массив согласован при присвоении.)
Сама ссылка на массив помечена как volatile
, так что читателям не нужно использовать блокировку для просмотра изменений в указанном массиве. (РЕДАКТИРОВАТЬ: Кроме того, volatile
гарантирует, что назначение не будет переупорядочено, что приведет к выполнению назначения, когда массив, возможно, находится в несогласованном состоянии.)
Блокировка записи требуется для предотвращение одновременной модификации, которая может привести к потере несогласованных данных или изменений в массиве.