java - example - ¿Cómo puede CopyOnWriteArrayList ser seguro para subprocesos?
java synchronized block (2)
He echado un vistazo al código fuente OpenJDK de CopyOnWriteArrayList
y parece que todas las operaciones de escritura están protegidas por el mismo bloqueo y las operaciones de lectura no están protegidas en absoluto. Según tengo entendido, en JMM todos los accesos a una variable (tanto de lectura como de escritura) deben estar protegidos por bloqueo o pueden producirse nuevos efectos de reordenación.
Por ejemplo, el método set(int, E)
contiene estas líneas (bajo bloqueo):
/* 1 */ int len = elements.length;
/* 2 */ Object[] newElements = Arrays.copyOf(elements, len);
/* 3 */ newElements[index] = element;
/* 4 */ setArray(newElements);
El método get(int)
, por otro lado, solo return get(getArray(), index);
.
En mi comprensión de JMM, esto significa que get
puede observar la matriz en un estado inconsistente si las sentencias 1-4 se reordenan como 1-2 (nuevo) -4-2 (copyOf) -3.
¿Entiendo JMM incorrectamente o hay alguna otra explicación sobre por qué CopyOnWriteArrayList
es seguro para subprocesos?
Obtener la referencia de matriz es una operación atómica. Entonces, los lectores verán la matriz anterior o la nueva matriz, de cualquier manera el estado es consistente. ( set(int,E)
calcula los nuevos contenidos de la matriz antes de establecer la referencia, por lo que la matriz es consistente cuando se realiza la asignación).
La referencia de matriz en sí misma está marcada como volatile
por lo que los lectores no necesitan usar un candado para ver los cambios en la matriz a la que se hace referencia. (EDITAR: Además, la volatile
garantiza que la asignación no se reordena, lo que daría lugar a que la asignación se realice cuando la matriz posiblemente esté en un estado incoherente).
El bloqueo de escritura es necesario para evitar modificaciones concurrentes, lo que puede ocasionar que la matriz contenga datos incoherentes o se pierdan los cambios.
Si observa la referencia de matriz subyacente, verá que está marcada como volatile
. Cuando se produce una operación de escritura (como en el extracto anterior), esta referencia volatile
solo se actualiza en la declaración final a través de setArray
. Hasta este punto, cualquier operación de lectura devolverá elementos de la copia anterior de la matriz.
El punto importante es que la actualización de la matriz es una operación atómica y, por lo tanto, las lecturas siempre verán la matriz en un estado consistente.
La ventaja de solo sacar un candado para las operaciones de escritura es un rendimiento mejorado para las lecturas: esto se debe a que las operaciones de escritura para una CopyOnWriteArrayList
pueden ser muy lentas ya que implican copiar toda la lista.