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

EDITAR IMPORTANTE Sé sobre el “sucede antes” en el hilo donde están ocurriendo las dos asignaciones; mi pregunta es si otro hilo estaría leyendo “b” no nulo mientras que “a” sigue siendo nulo. Así que sé que si llama a doIt () desde el mismo hilo que el que llamó previamente a setBothNonNull (…), entonces no puede lanzar una NullPointerException. Pero, ¿qué ocurre si uno llama a doIt () desde otro hilo que el que llama 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 la sincronización” porque no tengo ningún problema que resolver: simplemente quiero entender las garantías volatile (o falta de garantías) con respecto a la ejecución fuera de orden).

Digamos que tenemos un objeto que contiene dos referencias de cadenas volatile que el constructor inicializa como nulo y que tenemos una sola forma de modificar las dos cadenas: llamando a setBoth (…) y que luego solo podemos establecer sus referencias en non -null reference (solo el constructor puede establecerlos en null).

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

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

Entonces, si hago esto (una vez más, no hay duda, la pregunta viene a continuación):

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

¿Estoy en lo correcto 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 leo una “b” no nula, ¿leeré una “a” no nula?

Porque debido al procesador (multi) fuera de servicio y la forma en que volatile obras volatile “b” podrían 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 ¿no? (una vez más, el “problema” se hace a propósito para intentar comprender la semántica de la palabra clave volatile y el Modelo de memoria de Java, no para resolver un problema).

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

 a = one; b = two; 

Las instrucciones 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 comstackdor podría hacer un reordenamiento si solo hubiera un subproceso A. Sin embargo, HAY otros subprocesos y PUEDEN detectar el reordenamiento. Es por eso que el comstackdor NO puede hacer ese reordenamiento. El modelo de memoria de Java requiere que el comstackdor se asegure específicamente de que ningún hilo detecte dicho reordenamiento.

Pero, ¿qué ocurre si uno llama a doIt () desde otro hilo que el que llama a setBothNonNull (…)?

No, NUNCA NUNCA tendrás un NPE. volatile semántica volatile impone el orden entre hilos. Lo que significa que, para todos los hilos existentes, la asignación de one ocurre antes de la asignación de two .

¿Estoy en lo correcto 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 leo una “b” no nula, ¿leeré una “a” no nula?

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

( 1 ) Si xey son acciones del mismo hilo y x viene antes de y en el orden del progtwig, entonces hb (x, y).

( 2 ) Si una acción x se sincroniza, con una acción siguiente y, 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 subsecuentes de v por cualquier hilo (donde subsecuente se define según el orden de sincronización).

Teorema

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

Prueba informal

  1. Por ( 1 ) – hb (escribir (a, no nulo), escribir (b, no nulo)) en el hilo # 1
  2. Por ( 2 ) y ( 4 ) – hb (escribir (b, no nulo), leer (b, no nulo))
  3. Por ( 1 ) – hb (leer (b, no nulo), leer (a, XXX)) en el hilo # 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 “pasa antes que” la lectura del valor (XXX) de a . La única forma en que XXX puede ser nulo es si hubo alguna otra acción escribiendo nulo a tal que hb (write (a, non-null), write (a, XXX)) y hb (write (a, XXX), leer (a, XXX)). Y esto es imposible según la definición del problema, y ​​por lo tanto XXX no puede ser nulo. QED.

Explicación : el JLS establece que la relación hb (…) (“sucede antes”) no prohíbe totalmente el reordenamiento. Sin embargo, si hb (xx, yy), el reordenamiento de las acciones xx e yy solo está permitido si el código resultante tiene el mismo efecto observable que la secuencia original.

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

Si bien las respuestas aceptadas de Stephen C son buenas y lo cubren bastante, cabe destacar que la variable a no tiene que ser volátil, y aún así no obtendrás un NPE. Esto se debe a que habrá una relación de pasar antes entre a = one b = two , independientemente de si a es volatile . Así que la prueba formal de Stephen C todavía se aplica, simplemente no hay necesidad de ser volátil.

Leí esta página 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).