una puede poo modificador metodos metodo instanciar finales clases clase java multithreading concurrency

java - poo - se puede instanciar una clase final



¿Qué garantiza la palabra clave final con respecto a la concurrencia? (6)

Creo que he leído que la palabra clave final en un campo garantiza que si el hilo 1 ejemplifica el objeto que contiene el campo, el hilo 2 siempre verá el valor inicializado de ese campo si el hilo 2 tiene una referencia al objeto (siempre que sea construido correctamente). También dice en el JLS que

[Subproceso 2] también verá las versiones de cualquier objeto o matriz a la que hagan referencia los campos finales que están al menos tan actualizados como los campos finales. (sección 17.5 de JLS)

Eso implica que si tengo clase A

class A { private final B b = new B(); private int aNotFinal = 2; ...

y clase B

class B { private final int bFinal = 1; private int bNotFinal = 2; ...

entonces no se garantiza que aNotFinal se inicialice en el momento en que el hilo 2 obtiene una referencia a la clase A, pero el campo bNotFinal es, porque B es un objeto referenciado por un campo final, como se especifica en el JLS.

¿Tengo esto bien?

Editar:

Un escenario donde esto podría suceder sería si tenemos dos hilos ejecutando simultáneamente getA () en la misma instancia de una clase C.

class C { private A a; public A getA(){ if (a == null){ // Thread 1 comes in here because a is null. Thread B doesn''t come in // here because by the time it gets here, object c // has a reference to a. a = new A(); } return a; // Thread 2 returns an instance of a that is not fully // initialized because (if I understand this right) JLS // does not guarantee that non-final fields are fully // initialized before references get assigned } }


"JSR 133 (Java Memory Model) FAQ, Jeremy Manson y Brian Goetz, febrero de 2004" describe cómo funciona el campo final .

CITAR:

Los objetivos de JSR 133 incluyen:

  • 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.

Creo que su pregunta es respondida por JLS justo debajo de la parte que cita, en la Sección 17.5.1: Semántica de los campos finales :

Dada una escritura w , una congelación f , una acción a (que no es una lectura de un campo final), una lectura r 1 del campo final congelado por f , y una lectura r 2 tal que hb ( w , f ), hb ( f , a ), mc ( a , r 1 ) y desreferencias ( r 1 , r 2 ), luego al determinar qué valores se pueden ver por r 2 , consideramos hb ( w , r 2 ).

Desglosémoslo en términos de la pregunta:

  • w : es la escritura en bNotFinal por el subproceso 1
  • f : es la acción de congelación que congela b
  • a : publicación de la referencia de objeto A
  • r 1 : una lectura de b (congelado por f ) por el Subproceso 2
  • r 2 : un b.bNotFinal de b.bNotFinal por el Subproceso 2

Notamos eso

  • hb ( w , f ): la escritura en bNotFinal ocurre antes de la congelación de b
  • hb ( f , a ): la referencia A se publica después de que el constructor finaliza (es decir, después de la congelación)
  • mc ( a , r 1 ): El hilo 2 lee la referencia A antes de leer el Ab
  • dereferencias ( r 1 , r 2 ): el subproceso 2 elimina el valor de b al acceder a b.bNotFinal

La siguiente oración ...

"entonces, al determinar qué valores se pueden ver por r 2 , consideramos hb ( w , r 2 )"

... luego se traduce a

al determinar qué valores se pueden ver por la lectura de b.bNotFinal , consideramos que la escritura en bNotFinal por el Subproceso 1 ocurre antes de la lectura de b.bNotFinal .

Es decir, el subproceso 2 está garantizado para ver el valor 2 para b.bNotFinal .

Una cita relevante de Bill Pugh:

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".

En particular, esta es una respuesta directa al ejemplo @supercat planteado sobre el intercambio no sincronizado de referencias de String .


Lo que dices es verdad.

Marcar un campo como final obliga al compilador a completar la inicialización del campo antes de que el constructor finalice. Sin embargo, no existe tal garantía para los campos que no son finales. Esto puede parecer extraño, sin embargo, el compilador y la JVM hacen muchas cosas para fines de optimización, como las instrucciones de reordenamiento, que hacen que algo así ocurra.

La palabra clave final tiene muchos más beneficios. De Java Concurecncy in Practice:

Los campos finales no se pueden modificar (aunque los objetos a los que hacen referencia se pueden modificar si son mutables), pero también tienen una semántica especial en el Modelo de memoria de Java. Es el uso de los campos finales lo que hace posible la garantía de seguridad de inicialización (ver Sección 3.5.2) que permite que los objetos inmutables sean accedidos libremente y compartidos sin sincronización.

Los libros dicen entonces:

Para publicar un objeto de forma segura, tanto la referencia al objeto como el estado del objeto deben hacerse visibles para otros hilos al mismo tiempo. Un objeto correctamente construido puede ser publicado de manera segura por:

  • 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 bloqueo.

Vale la pena mencionar que la final aquí sirve para el mismo propósito que la volatile en términos de visibilidad de los valores a los hilos. Dicho esto, no puede usar tanto final como volatile en un campo ya que son redundantes entre sí. De vuelta a tu pregunta. Como otros han señalado, su suposición es incorrecta ya que JLS solo garantiza la visibilidad de la referencia a B, no los campos no finales definidos en B. Sin embargo, puede hacer que B se comporte de la manera que usted desea que se comporte. Una solución es declarar bNotFinal como volatile si no puede ser final .


Hay una barrera de memoria insertada después de una asignación final (es una valla de la tienda), así es como se garantiza que los otros subprocesos verán el valor que usted asigna. Me gusta el JLS y cómo dice que las cosas se hacen con "happen-before / after" y garantías de la final, pero para mí, la barrera de la memoria y sus efectos son mucho más simples de comprender.

Deberías leer esto: barreras de memoria


class A { private final B b = new B(); }

La línea anterior solo garantiza que b se inicializará cuando acceda a b desde una instancia de A. Ahora, el detalle de la inicialización de b o cualquier instancia de B depende completamente de cómo se define B y en este caso puede inicializarse según JLS o puede que no.

Entonces, si haces A a = new A (); de un hilo y de alguna manera logras leer ab de otro hilo, se garantiza que no verás nulo si a no es nulo, pero b.bNotFinal puede seguir siendo cero.