Как CopyOnWriteArrayList может быть потокобезопасным?
Я рассмотрел исходный код 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
является потокобезопасным?
Ответы
Ответ 1
Если вы посмотрите на ссылку базового массива, вы увидите ее как volatile
. Когда происходит операция записи (например, в приведенном выше извлечении) эта ссылка volatile
обновляется только в финальном выражении через setArray
. До этого момента любые операции чтения возвращают элементы из старой копии массива.
Важным моментом является то, что обновление массива - это атомная операция, и поэтому чтение всегда будет видеть массив в согласованном состоянии.
Преимущество только снятия блокировки для операций записи улучшает пропускную способность для чтения: это потому, что операции записи для CopyOnWriteArrayList
могут быть очень медленными, поскольку они включают в себя копирование всего списка.
Ответ 2
Получение ссылки на массив - это атомная операция. Таким образом, читатели либо видят старый массив, либо новый массив - в любом случае состояние согласовано. (set(int,E)
вычисляет новое содержимое массива перед установкой ссылки, поэтому массив согласован, когда выполняется присвоение.)
Сама ссылка на массив помечена как volatile
, так что читателям не нужно использовать блокировку для просмотра изменений в ссылочном массиве. (EDIT: Кроме того, volatile
гарантирует, что присваивание не будет переупорядочено, что приведет к выполнению задания, когда массив, возможно, находится в несогласованном состоянии.)
Блокировка записи требуется для предотвращения одновременной модификации, что может привести к тому, что массив будет удерживать несогласованные данные или изменения, потерянные.