El Javadoc en Guava ImmutableList dice que la clase tiene las propiedades de Guava ImmutableCollection , uno de los cuales es la seguridad de rosca:
Seguridad de los hilos. Es seguro para acceder a esta colección simultáneamente desde varios subprocesos.
Pero vistazo a cómo la ImmutableList
está construido por su constructor - El Builder
guarda todos los elementos en un Object[]
(Eso es bueno, ya nadie dijo que el constructor era seguro para hilos) y sobre la construcción pasa a esa matriz (o, posiblemente, una copia) al constructor de RegularImmutableList :
public abstract class ImmutableList<E> extends ImmutableCollection<E>
implements List<E>, RandomAccess {
...
static <E> ImmutableList<E> asImmutableList(Object[] elements, int length) {
switch (length) {
case 0:
return of();
case 1:
return of((E) elements[0]);
default:
if (length < elements.length) {
elements = Arrays.copyOf(elements, length);
}
return new RegularImmutableList<E>(elements);
}
}
...
public static final class Builder<E> extends ImmutableCollection.Builder<E> {
Object[] contents;
...
public ImmutableList<E> build() { //Builder's build() method
forceCopy = true;
return asImmutableList(contents, size);
}
...
}
}
¿Qué RegularImmutableList
hacer con estos elementos? Lo que se espera, simplemente inicia su matriz interna, que luego se utiliza para todas las elaboraciones leer:
class RegularImmutableList<E> extends ImmutableList<E> {
final transient Object[] array;
RegularImmutableList(Object[] array) {
this.array = array;
}
...
}
¿Cómo es esta caja fuerte del hilo sea? Lo que garantiza la sucede, antes de relación entre las escrituras a cabo en el Builder
y la lee desde RegularImmutableList
?
De acuerdo con el modelo de memoria de Java hay una relación sucede-antes sólo en cinco casos (desde el Javadoc para java.util.concurrent
):
- Cada acción en un hilo sucede-antes de cada acción en ese hilo que viene más adelante con el fin del programa.
- Una de desbloqueo (bloque o método sincronizado salida) de un monitor de pasa-antes de cada bloqueo posterior de ese mismo monitor (bloque o entrada método sincronizado). Y debido a que la sucede, antes de relación es transitiva, todas las acciones de un hilo antes de desbloqueo ocurren antes todas las acciones posteriores a cualquier hilo de bloqueo ese monitor.
- Una escritura en un campo volátil sucede-antes de cada lectura posterior de ese mismo campo. Escribe y lee de campos volátiles tienen efectos de coherencia de memoria similares a entrar y salir de monitores, pero no implican la exclusión mutua de bloqueo.
- Una llamada a comenzar en un hilo sucede-antes de cualquier acción en el hilo comenzado.
- Todas las acciones en un hilo suceden-antes de que cualquier otro hilo regresa con éxito a partir de una combinación en ese hilo.
Ninguno de ellos parece aplicarse aquí. Si algún hilo construye la lista y pasa su referencia a algunos otros hilos sin utilizar cerraduras (por ejemplo a través de una final
o volatile
campo), no veo lo que garantiza de hilo de seguridad. ¿Qué me estoy perdiendo?
Editar:
Sí, la escritura de la referencia a la matriz es seguro para subprocesos en razón de que sea final
. De manera que es hilo claramente segura. Lo que me pregunto acerca eran las escrituras de los elementos individuales. Los elementos de la matriz son ni final
ni volatile
. Sin embargo, ellos parecen estar escrito por un hilo y leído por otro sin sincronización.
Así que la pregunta se puede reducir a "si el subproceso A escribe en un final
campo, hace que la garantía de que otros hilos verán que no sólo escribir, sino todas las escrituras anteriores de A así?"
JMM garantiza la inicialización de seguridad (todos los valores inicializados en el constructor serán visibles para los lectores) si todos los campos del objeto son final
y no hay fugas de this
desde el constructor 1 :
class RegularImmutableList<E> extends ImmutableList<E> {
final transient Object[] array;
^
RegularImmutableList(Object[] array) {
this.array = array;
}
}
El campo final semántica garantiza que los lectores verán una serie hasta a la fecha:
Los efectos de todas las inicializaciones deben estar comprometidos con la memoria antes de cualquier código constructor después publica la referencia al objeto de nueva construcción.
Gracias a @JBNizet y para @chrylis para el enlace a los JLS.
1 - "Si esto es seguido, a continuación, cuando el objeto es visto por otro hilo, que el hilo siempre ver la versión correctamente construida de campos finales de ese objeto. También ver versiones de cualquier objeto o matriz referenciado por aquellos campos finales que son al menos como hasta a la fecha que los campos son finales ". - JLS §17.5 .