¿Sucede antes relaciones con campos volátiles y bloques sincronizados en Java y su impacto en variables no volátiles?

Todavía soy bastante nuevo en el concepto de enhebrar y trato de entender más sobre él. Recientemente, me encontré con una publicación de blog sobre What Volatile Means en Java de Jeremy Manson, donde escribe:

Cuando un hilo escribe en una variable volátil, y otro hilo ve esa escritura, el primer hilo le dice al segundo sobre todos los contenidos de la memoria hasta que realiza la escritura en esa variable volátil. […] todos los contenidos de la memoria vistos por el Subproceso 1, antes de escribir en [volatile] ready , deben estar visibles para el Subproceso 2, después de que lea el valor true para ready . [énfasis añadido por mí]

Ahora, ¿eso significa que todas las variables (volátiles o no) retenidas en la memoria del subproceso 1 en el momento de la escritura en la variable volátil serán visibles para el subproceso 2 después de leer esa variable volátil? Si es así, ¿ es posible descifrar esa afirmación de la documentación oficial de Java / orígenes de Oracle? ¿Y desde qué versión de Java funcionará esto?

En particular, si todos los subprocesos comparten las siguientes variables de clase:

 private String s = "running"; private volatile boolean b = false; 

Y el hilo 1 ejecuta lo siguiente primero:

 s = "done"; b = true; 

Y el hilo 2 luego se ejecuta luego (después de que el hilo 1 escribió en el campo volátil):

 boolean flag = b; //read from volatile System.out.println(s); 

¿Esto se garantizaría para imprimir “hecho”?

¿Qué pasaría si en lugar de declarar b como volatile pongo la escritura y la lectura en un bloque synchronized ?

Además, en una discusión titulada ” ¿Las variables estáticas se comparten entre subprocesos? “, @TREE escribe :

No use volátiles para proteger más de una pieza del estado compartido.

¿Por qué? (Lo siento, todavía no puedo comentar sobre otras preguntas, o habría preguntado allí …)

Sí, se garantiza que el hilo 2 imprimirá “hecho”. Por supuesto, eso es si la escritura en b en el Subproceso 1 realmente sucede antes de la lectura de b en el Subproceso 2, en lugar de suceder al mismo tiempo o antes.

El corazón del razonamiento aquí es la relación de pasar antes . Las ejecuciones de progtwigs multiproceso se consideran como hechos de eventos. Los eventos se pueden relacionar por relaciones de pasar antes que decir que un evento sucede antes que otro. Incluso si dos eventos no están directamente relacionados, si puede rastrear una cadena de relaciones de pasar antes de un evento a otro, entonces puede decir que una sucede antes que la otra.

En tu caso, tienes los siguientes eventos:

  • El hilo 1 escribe para s
  • El hilo 1 escribe en b
  • El hilo 2 lee de b
  • El hilo 2 lee de s

Y las siguientes reglas entran en juego:

  • “Si xey son acciones del mismo hilo y x viene antes de y en el orden del progtwig, entonces hb (x, y)”. (la regla de orden del progtwig )
  • “Una escritura en un campo volátil (§8.3.1.4) ocurre, antes de cada lectura posterior de ese campo”. (la regla volátil )

Sucede lo siguiente: antes de que existan las relaciones:

  • El subproceso 1 escribe que s ocurre antes de que el subproceso 1 escriba en b (regla de orden de progtwig)
  • El subproceso 1 escribe en b antes de que el subproceso 2 lea desde b (regla volátil)
  • El hilo 2 lee de b sucede antes de que el hilo 2 lea de s (regla de orden de progtwig)

Si sigues esa cadena, puedes ver eso como resultado:

  • El subproceso 1 escribe que s ocurre antes de que el subproceso 2 lea desde s

¿Qué pasaría si en lugar de declarar b como volátil pongo la escritura y la lectura en un bloque sincronizado?

Si y solo si protege todos esos bloques sincronizados con el mismo candado, tendrá la misma garantía de visibilidad que su ejemplo volatile . Además, tendrá exclusión mutua de la ejecución de dichos bloques sincronizados.

No use volátiles para proteger más de una pieza del estado compartido.

¿Por qué?

volatile no garantiza la atomicidad: en su ejemplo, la variable s también puede haber sido mutada por otros hilos después de la escritura que está mostrando; el hilo de lectura no tendrá ninguna garantía en cuanto a qué valor ve. Lo mismo ocurre con las escrituras que ocurren después de leer el volatile , pero antes de la lectura de s .

Lo que es seguro hacer y lo que se hace en la práctica es compartir un estado inmutable accesible transitivamente desde la referencia escrita a una variable volatile . Así que tal vez ese es el significado pretendido por “una pieza de estado compartido”.

¿Es posible confundir esa afirmación con la documentación oficial de Java / las fonts de Oracle?

Citas de la especificación:

17.4.4. Orden de sincronizacion

Una escritura en una variable volátil v (§8.3.1.4) se sincroniza con todas las lecturas subsecuentes de v por cualquier hilo (donde “subsecuente” se define según el orden de sincronización).

17.4.5. Sucede antes de la orden

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

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

Esto debería ser suficiente.

¿Y desde qué versión de Java funcionará esto?

Java Language Specification, 3rd Edition introdujo la reescritura de la especificación del modelo de memoria que es la clave de las garantías anteriores. NB la mayoría de las versiones anteriores actuaban como si las garantías estuvieran allí y muchas líneas de código realmente dependieran de ello. La gente se sorprendió cuando descubrieron que las garantías, de hecho, no habían estado allí.

¿Esto se garantizaría para imprimir “hecho”?

Como se dijo en Java Concurrency in Practice :

Cuando el hilo A escribe en una variable volatile y posteriormente el hilo B lee esa misma variable, los valores de todas las variables que fueron visibles para A antes de escribir en la variable volatile vuelven visibles para B después de leer la variable volatile .

Entonces, , esto garantiza imprimir “hecho”.

¿Qué pasaría si en lugar de declarar b como volátil pongo la escritura y la lectura en un bloque sincronizado?

Esto también garantizará lo mismo.

No use volátiles para proteger más de una pieza del estado compartido.

¿Por qué?

Porque, volátil garantiza solo visibilidad. No garantiza la atomicidad. Si tenemos dos escrituras volátiles en un método al que se accede por un hilo A y otro hilo B está accediendo a esas variables volátiles, entonces mientras el hilo A está ejecutando el método, es posible que el hilo A sea ​​reemplazado por el hilo B en el medio de operaciones (por ejemplo, después de la primera escritura volátil pero antes de la segunda escritura volátil por el hilo A ). Entonces, para garantizar la atomicidad de la operación, la synchronization es la salida más factible.