explicacion example java volatile java-memory-model

java - example - Garantías volátiles y ejecución fuera de orden



synchronized java (5)

Esta pregunta ya tiene una respuesta aquí:

EDICIÓN IMPORTANTE Sé que "sucede antes" en el subproceso donde están ocurriendo las dos asignaciones, mi pregunta es si sería posible que otro subproceso esté leyendo "b" no nulo, mientras que "a" aún es nulo. Por lo tanto, sé que si está llamando a doIt () desde el mismo subproceso que anteriormente llamó a setBothNonNull (...), entonces no puede lanzar una NullPointerException. ¿Pero qué pasa si uno está llamando a doIt () desde otro hilo que no sea el que está llamando a setBothNonNull (...) ?

Tenga en cuenta que esta pregunta es únicamente sobre la palabra clave volatile y las garantías volatile : no se trata de la palabra clave synchronized (así que no responda "debe usar sincronizar" porque no tengo ningún problema que resolver: simplemente quiero comprender las garantías volatile (o falta de garantías) con respecto a la ejecución fuera de orden).

Supongamos que tenemos un objeto que contiene dos referencias de cadena volatile que el constructor inicializa en nulo y que solo tenemos una forma de modificar las dos cadenas: llamando a setBoth (...) y que solo podemos establecer sus referencias después a no - referencia nula (solo el constructor puede establecerlos en nulo).

Por ejemplo (es solo un ejemplo, todavía no hay ninguna pregunta):

public class SO { private volatile String a; private volatile String b; public SO() { a = null; b = null; } public void setBothNonNull( @NotNull final String one, @NotNull final String two ) { a = one; b = two; } public String getA() { return a; } public String getB() { return b; } }

En setBothNoNull (...) , la línea que asigna el parámetro no nulo "a" aparece antes de la línea que asigna el parámetro no nulo "b".

Luego, si hago esto (una vez más, no hay duda, la pregunta es la siguiente):

doIt() { if ( so.getB() != null ) { System.out.println( so.getA().length ); } }

¿Tengo razón al entender que, debido a una ejecución fuera de orden, puedo obtener una NullPointerException ?

En otras palabras: no hay garantía de que porque lea una "b" que no es nula, leeré una "a" que no sea nula.

¿Debido a que el procesador (multi) fuera de servicio y la forma en que funciona la volatile "b" podría asignarse antes de "a"?

volatile garantías volatile que se leen después de una escritura siempre verán el último valor escrito, pero aquí hay un "problema" fuera de orden, ¿verdad? (Una vez más, el "problema" se hace a propósito para tratar de comprender la semántica de la palabra clave volatile y el modelo de memoria de Java, no para resolver un problema).


¿Tengo razón al entender que, debido a una ejecución fuera de orden, puedo obtener una NullPointerException? En otras palabras: no hay garantía de que porque lea una "b" que no es nula, leeré una "a" que no sea nula.

Suponiendo que los valores asignados a a y b o no sean nulos, creo que su comprensión no es correcta . El JLS dice esto:

( 1 ) Si x e y son acciones del mismo hilo y x aparece antes de y en el orden del programa, entonces hb (x, y).

( 2 ) Si una acción x se sincroniza con una acción siguiente y, entonces también tenemos hb (x, y).

( 3 ) Si hb (x, y) y hb (y, z), entonces hb (x, z).

y

( 4 ) Una escritura en una variable volátil (§8.3.1.4) v sincroniza con todas las lecturas subsiguientes de v por cualquier hilo (donde subsiguiente se define de acuerdo con el orden de sincronización).

Teorema

Dado que el hilo # 1 ha llamado setBoth(...); una vez, y que los argumentos no eran nulos, y que el subproceso # 2 ha observado que b no es nulo, entonces el subproceso # 2 no puede observar que a sea nulo.

Prueba informal

  1. Por ( 1 ) - hb (escribir (a, no nulo), escribir (b, no nulo)) en el subproceso # 1
  2. Por ( 2 ) y ( 4 ) - hb (escribir (b, no nulo), leer (b, no nulo))
  3. Por ( 1 ) - hb (lea (b, no nulo), lea (a, XXX)) en el subproceso # 2,
  4. Por ( 4 ) - hb (escribir (a, no nulo), leer (b, no nulo))
  5. Por ( 4 ) - hb (escribir (a, no nulo), leer (a, XXX))

En otras palabras, la escritura de un valor no nulo a a "sucede antes de" la lectura del valor (XXX) de a . La única forma en que XXX puede ser nulo, es si hubiera alguna otra acción escribiendo null a tal que hb (write (a, non-null), write (a, XXX)) y hb (write (a, XXX), leer (a, XXX)). Y esto es imposible de acuerdo con la definición del problema y, por lo tanto, XXX no puede ser nulo. QED.

Explicación : el JLS indica que la relación hb (...) ("sucede antes de") no prohíbe totalmente la reordenación. Sin embargo, si hb (xx, yy), la reordenación de las acciones xx y yy solo se permite si el código resultante tiene el mismo efecto observable que la secuencia original.


Encontré la siguiente publicación que explica que volatile tiene la misma semántica de ordenación que la sincronizada en este caso. Java volátil es potente


Leí esta page y encontré una versión no volátil y no sincronizada de su pregunta:

class Simple { int a = 1, b = 2; void to() { a = 3; b = 4; } void fro() { System.out.println("a= " + a + ", b=" + b); } }

fro puede obtener 1 o 3 para el valor de a , e independientemente puede obtener 2 o 4 para el valor de b .

(Me doy cuenta de que esto no responde a tu pregunta, pero la complementa).


No, nunca obtendrás una NPE. Esto se debe a que volatile también tiene el efecto memoria de introducir una relación de suceso antes. En otras palabras, evitará la reordenación de

a = one; b = two;

Las declaraciones anteriores no se volverán a ordenar y todos los subprocesos observarán el valor one para a si b ya tiene el valor two .

Aquí hay un hilo en el que David Holmes explica esto:
http://markmail.org/message/j7omtqqh6ypwshfv#query:+page:1+mid:34dnnukruu23ywzy+state:results

EDITAR (respuesta al seguimiento): Lo que Holmes está diciendo es que, en teoría , el compilador podría hacer un pedido si solo hubiera un subproceso A. Sin embargo, hay otros subprocesos, y PUEDEN detectar el reordenamiento. Es por eso que al compilador NO se le permite hacer esa reordenación. El modelo de memoria java requiere el compilador específicamente para asegurarse de que ningún subproceso detectará tal reordenamiento.

¿Pero qué pasa si uno está llamando a doIt () desde otro hilo que no sea el que está llamando a setBothNonNull (...)?

No, usted NUNCA tendrá una NPE. volatile semántica volatile impone el ordenamiento entre hilos. Lo que significa que, para todo el hilo existente, la asignación de one ocurre antes de la asignación de two .


Si bien Stephen C y las respuestas aceptadas son buenas y casi lo cubren, vale la pena hacer una nota importante de que la variable a no tiene que ser volátil, y aún así no obtendrá una NPE. Esto se debe a que habrá una relación de suceso antes entre a = one y b = two , independientemente de si a es volatile . Así que la prueba formal de Stephen C todavía se aplica, pero no hay necesidad de a ser volátil.