Concurrencia de Java: es el campo final (inicializado en el constructor) thread-safe?

¿Alguien puede decirme si esta clase es segura o no?

class Foo { private final Map aMap; public Foo() { aMap = new HashMap(); aMap.put("1", "a"); aMap.put("2", "b"); aMap.put("3", "c"); } public String get(String key) { return aMap.get(key); } } 

Editar: Es mi culpa no aclarar la pregunta. De acuerdo con las preguntas frecuentes de JMM :

Se debe proporcionar una nueva garantía de seguridad de inicialización. Si un objeto está construido correctamente (lo que significa que las referencias a él no se escapan durante la construcción), todos los hilos que ven una referencia a ese objeto también verán los valores para sus campos finales que se establecieron en el constructor, sin la necesidad de sincronización.

Esto me hizo confundir que el conjunto de aMap es aMap = new HashMap(); . Para que otros hilos puedan ver estos

 aMap.put("1", "a"); aMap.put("2", "b"); aMap.put("3", "c"); 

o no ?

Editar: Encontré esta pregunta exactamente cerrada a mi pregunta

Como ya se señaló, es absolutamente seguro para subprocesos, y el final es importante aquí debido a sus efectos de visibilidad de memoria.

Presencia de garantías final que otros subprocesos verían valores en el mapa después de que el constructor finalice sin ninguna sincronización externa. Sin final , no se puede garantizar en todos los casos, y deberá utilizar expresiones de publicación seguras al hacer que los objetos recién construidos estén disponibles para otros hilos, concretamente (de Java Concurrency in Practice ):

  • Inicializando una referencia de objeto desde un inicializador estático;
  • Almacenar una referencia en un campo volátil o AtomicReference;
  • Almacenar una referencia en un campo final de un objeto construido correctamente; o
  • Almacenar una referencia en un campo que está adecuadamente protegido por un locking.

Sí lo es. No hay forma de modificar la referencia aMap sí misma o agregarla al mapa después del constructor (excluyendo la reflexión).

Si expone un aMap , no lo será, porque dos hilos podrían modificar el mapa al mismo tiempo.

Puede mejorar su clase haciendo que un aMap no aMap modificarse mediante Collections.unmodifiableCollection o Collections.unmodifiableMap .

La guayaba tiene clases inmutables para hacer este tipo de cosas más fácil y garantizado inmutable:

 private final ImmutableMap aMap = ImmutableMap.of( "1", "a", "2", "b", "3", "c"); 

Esta clase no tiene problemas de concurrencia porque expone solo un método get. Si agrega algún método que modifique el mapa, debe marcar este método como synchronized .

Sí lo es, siempre que esta sea toda la definición de la clase y no un fragmento de la misma .

El hecho clave es que no hay forma de que los contenidos de aMap puedan modificarse después de la construcción.

Como es ahora, debería ser seguro para subprocesos. Sin embargo, si agrega otros métodos que modifican el hashmap, entonces no.

No creo que el fragmento de código anterior sea seguro para subprocesos. La única línea que es código seguro es

 aMap = new HashMap(); 

Como se muestra en el ejemplo http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html ,

 class FinalFieldExample { final int x; int y; static FinalFieldExample f; public FinalFieldExample() { x = 3; y = 4; } static void writer() { f = new FinalFieldExample(); } static void reader() { if (f != null) { int i = fx; // x is guaranteed to be 3 int j = fy; // y can have any value } } } 

Esto significa que una vez que se inicializan los campos finales, no se garantiza la seguridad de los hilos. Dado que solo la asignación de referencia está garantizada para ser segura para hilos y el objeto en sí mismo puede ser mutable según su ejemplo. La siguiente statement puede no ser segura para subprocesos

 aMap.put("1", "a"); aMap.put("2", "b"); aMap.put("3", "c"); 

EDIT Mi mal vi los comentarios debajo del código después

La capacidad de ver el valor correctamente construido para el campo es agradable, pero si el campo en sí es una referencia, también quiere que su código vea los valores actualizados para el objeto (o matriz) al que apunta. Si su campo es un campo final, esto también está garantizado. Por lo tanto, puede tener un puntero final a una matriz y no tener que preocuparse de que otros subprocesos vean los valores correctos para la referencia de matriz, pero valores incorrectos para los contenidos de la matriz. De nuevo, por “correcto” aquí, queremos decir “actualizado a partir del final del constructor del objeto”, no “el último valor disponible”.