Aquí :
Se considera que un objeto está completamente inicializado cuando finaliza su constructor. Se garantiza que un subproceso que solo puede ver una referencia a un objeto después de que ese objeto se haya inicializado por completo vea los valores correctamente inicializados para los campos finales de ese objeto.
¿Se tienen las mismas garantías para el campo volatile ? ¿Qué pasa si el campo y en el siguiente ejemplo fuera volatile , podríamos observar 0 ?
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; // guaranteed to see 3 int j = fy; // could see 0 } }}
Creo que leer 0 es posible.
La especificación dice :
Una escritura en una variable volátil
vse sincroniza con todas las lecturas posteriores devpor cualquier subproceso (donde "subsecuente" se define de acuerdo con el orden de sincronización).
En nuestro caso, tenemos una escritura y una lectura de la misma variable, pero no hay nada que asegure que la lectura sea posterior. En particular, la escritura y la lectura ocurren en hilos diferentes que no están relacionados por ninguna otra acción de sincronización.
Es decir, es posible que la lectura ocurra antes que la escritura en el orden de sincronización.
Esto puede sonar sorprendente dado que el subproceso de escritura escribe f después de y , y el subproceso de lectura lee y solo si detecta que se ha escrito f . Pero dado que la escritura y la lectura en f no están sincronizadas, se aplica la siguiente cita :
Más específicamente, si dos acciones comparten una relación pasa antes, no necesariamente tienen que parecer que han pasado en ese orden para cualquier código con el que no comparten una relación pasa antes. Las escrituras en un subproceso que están en una carrera de datos con lecturas en otro subproceso pueden, por ejemplo, parecer que ocurren fuera de orden para esas lecturas.
Las notas explicativas del ejemplo 17.4.1 también reafirman que el tiempo de ejecución puede reordenar estas escrituras:
Si alguna ejecución exhibiera este comportamiento, entonces sabríamos que la instrucción 4 vino antes de la instrucción 1, que vino antes de la instrucción 2, que vino antes de la instrucción 3, que vino antes de la instrucción 4. Esto es, a primera vista, absurdo.
Sin embargo, los compiladores pueden reordenar las instrucciones en cualquiera de los subprocesos, cuando esto no afecta la ejecución de ese subproceso de forma aislada.
En nuestro caso, el comportamiento del subproceso de escritura, de forma aislada, no se ve afectado por el reordenamiento de las escrituras en f e y .
Sí, es posible ver 0 cuando
class FinalFieldExample { final int x; volatile int y; static FinalFieldExample f; ... }La breve explicación:
writer() subproceso publica f = new FinalFieldExample() a través de una carrera de datosreader() vea el objeto f = new FinalFieldExample() como semi-inicializado.reader() puede ver un valor de y anterior a y = 4; — es decir, valor inicial 0 .Explicaciones más detalladas están aquí .
Puede reproducir este comportamiento en ARM64 con esta prueba jcstress .
Sí, 0 es posible cuando x es volátil, porque no hay garantía de que la escritura x = 3 en el subproceso writer() siempre suceda antes de la lectura local_f.x en el subproceso reader() .
class FinalFieldExample { volatile int x; static FinalFieldExample f; public FinalFieldExample() { x = 3; } static void writer() { f = new FinalFieldExample(); } static void reader() { var local_f = f; if (local_f != null) { int i = local_f.x; // could see 0 } } } Como resultado, aunque x es volatile (lo que significa que todas las lecturas y escrituras en x suceden en un orden global), nada impide que la lectura local_f.x en el subproceso del reader() ocurra antes que la escritura x = 3 en el writer() . writer() hilo.
local_f.x en este caso devolverá 0 (el valor predeterminado para int , que funciona como una escritura inicial).
El problema es que después de que el subproceso reader() lea f , no hay garantía (es decir, no sucede antes de la relación) de que vea el estado interno en f correctamente: es decir, es posible que no vea la escritura x = 3 en el campo interno fx hecho por el subproceso writer() en el constructor FinalFieldExample .
Puede crear esta relación antes de que suceda de la siguiente manera:
f volatile ( x puede hacerse no volátil) class FinalFieldExample { int x; static volatile FinalFieldExample f; ... } De la JLS :Se produce una escritura en un campo volátil (§8.3.1.4), antes de cada lectura posterior de ese campo.
x final en lugar de volatile class FinalFieldExample { final int x; static FinalFieldExample f; ... } De la JLS :Se considera que un objeto está completamente inicializado cuando finaliza su constructor. Se garantiza que un subproceso que solo puede ver una referencia a un objeto después de que ese objeto se haya inicializado por completo vea los valores correctamente inicializados para los campos finales de ese objeto.