Ejemplo más simple y comprensible de palabra clave volátil en Java

Estoy leyendo sobre palabras clave volátiles en Java y entiendo completamente la parte de la teoría.

Pero, lo que estoy buscando es un buen ejemplo de caso, que muestra lo que sucedería si la variable no fuera volátil y si lo fuera.

El siguiente fragmento de código no funciona como se esperaba ( de aioobe )

class Test extends Thread { boolean keepRunning = true; public void run() { while (keepRunning) { } System.out.println("Thread terminated."); } public static void main(String[] args) throws InterruptedException { Test t = new Test(); t.start(); Thread.sleep(1000); t.keepRunning = false; System.out.println("keepRunning set to false."); } } 

Idealmente, si keepRunning no era volátil, el hilo debería seguir ejecutándose indefinidamente. Pero, se detiene después de unos segundos.

Tengo dos preguntas básicas:

  • ¿Alguien puede explicar volátil con el ejemplo? No con la teoría de JLS.
  • ¿Es un sustituto volátil para la sincronización? ¿Logra la atomicidad?

Volátil -> Garantiza visibilidad y NO atomicidad

Sincronización (Bloqueo) -> Garantiza la visibilidad y la atomicidad (si se realiza correctamente)

Volátil no es un sustituto de la sincronización

Use la palabra volátil solo cuando esté actualizando la referencia y no realice otras operaciones en ella.

Ejemplo:

 volatile int i = 0; public void incrementI(){ i++; } 

no será seguro para subprocesos sin el uso de sincronización o AtomicInteger ya que el incremento es una operación compuesta.

¿Por qué el progtwig no se ejecuta indefinidamente?

Bueno, eso depende de varias circunstancias. En la mayoría de los casos, JVM es lo suficientemente inteligente como para eliminar los contenidos.

El uso correcto de volátiles discute varios usos posibles de volátiles. Usar el volátil correctamente es complicado, yo diría “En caso de duda, déjalo fuera”, utiliza el bloque sincronizado.

También:

el bloque sincronizado se puede usar en lugar de volátil pero el inverso no es verdadero .

Para su ejemplo particular: si no se declara volátil, la JVM del servidor podría levantar la variable keepRunning del bucle porque no se modifica en el bucle (convirtiéndolo en un bucle infinito), pero la JVM del cliente no lo haría. Es por eso que ves resultados diferentes.

La explicación general sobre las variables volátiles es la siguiente:

Cuando un campo se declara volatile , el comstackdor y el tiempo de ejecución reciben aviso de que esta variable se comparte y que las operaciones que se realizan en ella no se deben reordenar con otras operaciones de memoria. Las variables volátiles no se almacenan en caché en registros o en cachés donde están ocultos de otros procesadores, por lo que una lectura de una variable volátil siempre devuelve la escritura más reciente de cualquier subproceso .

Los efectos de visibilidad de las variables volátiles se extienden más allá del valor de la variable volátil en sí misma. Cuando el hilo A escribe en una variable volátil 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 volátil se vuelven visibles para B después de leer la variable volátil.

El uso más común para las variables volátiles es como indicador de finalización, interrupción o estado:

  volatile boolean flag; while (!flag) { // do something untill flag is true } 

Las variables volátiles se pueden usar para otros tipos de información de estado, pero se requiere más cuidado al intentar esto. Por ejemplo, la semántica de los volátiles no es lo suficientemente fuerte como para hacer que la operación de incremento ( count++ ) sea atómica, a menos que pueda garantizar que la variable está escrita solo desde un solo hilo.

El locking puede garantizar tanto la visibilidad como la atomicidad; las variables volátiles solo pueden garantizar la visibilidad.

Puede usar variables volátiles solo cuando se cumplan todos los siguientes criterios:

  • Las escrituras en la variable no dependen de su valor actual, o puede asegurarse de que solo un hilo actualiza el valor;
  • La variable no participa en invariantes con otras variables de estado; y
  • No se requiere locking por ninguna otra razón mientras se accede a la variable.

Sugerencia de depuración : asegúrese de especificar siempre el modificador de línea de comandos JVM -server al invocar la JVM, incluso para desarrollo y prueba. La JVM del servidor realiza más optimización que la JVM del cliente, como elevar variables fuera de un bucle que no se modifican en el bucle; el código que podría funcionar en el entorno de desarrollo (JVM del cliente) puede romperse en el entorno de despliegue (JVM del servidor).

Este es un extracto de Java Concurrency in Practice, el mejor libro que puedes encontrar sobre este tema.

He modificado tu ejemplo ligeramente. Ahora usa el ejemplo con keepRunning como miembro volátil y no volátil:

 class TestVolatile extends Thread{ //volatile boolean keepRunning = true; public void run() { long count=0; while (keepRunning) { count++; } System.out.println("Thread terminated." + count); } public static void main(String[] args) throws InterruptedException { TestVolatile t = new TestVolatile(); t.start(); Thread.sleep(1000); System.out.println("after sleeping in main"); t.keepRunning = false; t.join(); System.out.println("keepRunning set to " + t.keepRunning); } } 

¿Qué es palabra clave volátil?

palabra clave volátil evita el caching of variables .

Considere el código, primero sin palabra clave volátil

 class MyThread extends Thread { private boolean running = true; //non-volatile keyword public void run() { while (running) { System.out.println("hello"); } } public void shutdown() { running = false; } } public class Main { public static void main(String[] args) { MyThread obj = new MyThread(); obj.start(); Scanner input = new Scanner(System.in); input.nextLine(); obj.shutdown(); } } 

Idealmente , este progtwig debería print hello hasta que se presione la RETURN key . Pero en some machines puede suceder que la ejecución variable esté en cached y no se puede cambiar su valor del método shutdown (), lo que da como resultado infinite impresión infinite de texto hola.

Por lo tanto, al usar palabra clave volátil, se guaranteed que su variable no se almacenará en caché, es decir, se run fine en all machines .

 private volatile boolean running = true; //volatile keyword 

Por lo tanto, usar palabras clave volátiles es una safer programming practice good y safer programming practice .

Cuando una variable es volatile , garantiza que no se almacenará en caché y que los diferentes subprocesos verán el valor actualizado. Sin embargo, no marcarlo como volatile no garantiza la oposición. volatile fue una de esas cosas que se rompieron en la JVM durante mucho tiempo y aún no siempre se entienden bien.

Variable Volatile : palabra clave volátil es aplicable a las variables. La palabra clave volátil en Java garantiza que el valor de la variable volátil siempre se leerá desde la memoria principal y no desde la memoria caché local de Thread.

 Access_Modifier volatile DataType Variable_Name; 

Campo volátil: una indicación a la máquina virtual de que varios hilos pueden intentar acceder / actualizar el valor del campo al mismo tiempo. A un tipo especial de variables de instancia que debe compartirse entre todos los hilos con valor Modificado. Similar a la variable Estática (Clase), solo una copia del valor volátil se almacena en memoria caché, de modo que antes de realizar cualquier operación ALU, cada subproceso debe leer el valor actualizado de la memoria principal después de que la operación ALU tenga que escribir en la memoria principal. (Una escritura en una variable volátil v se sincroniza, con todas las lecturas subsecuentes de v por cualquier subproceso) Esto significa que los cambios en una variable volátil siempre son visibles para otros subprocesos.

enter image description here

Aquí a una nonvoltaile variable si Thread t1 cambia el valor en la caché de t1, Thread t2 no puede acceder al valor modificado hasta t1 escribe, t2 lee de la memoria principal para el valor modificado más reciente, lo que puede conducir a Data-Inconsistancy .

volátil no se puede almacenar en caché – ensamblador

  +--------------+--------+-------------------------------------+ | Flag Name | Value | Interpretation | +--------------+--------+-------------------------------------+ | ACC_VOLATILE | 0x0040 | Declared volatile; cannot be cached.| +--------------+--------+-------------------------------------+ |ACC_TRANSIENT | 0x0080 | Declared transient; not written or | | | | read by a persistent object manager.| +--------------+--------+-------------------------------------+ 

Shared Variables : la memoria que se puede compartir entre subprocesos se denomina memoria compartida o memoria de montón. Todos los campos de instancia, campos estáticos y elementos de matriz se almacenan en la memoria del montón.

Sincronización : sincronizado es aplicable a métodos, bloques. permite ejecutar solo 1 hilo a la vez en el objeto. Si t1 toma el control, los hilos restantes deben esperar hasta que libere el control.

Ejemplo:

 public class VolatileTest implements Runnable { private static final int MegaBytes = 10241024; private static final Object counterLock = new Object(); private static int counter = 0; private static volatile int counter1 = 0; private volatile int counter2 = 0; private int counter3 = 0; @Override public void run() { for (int i = 0; i < 5; i++) { concurrentMethodWrong(); } } void addInstanceVolatile() { synchronized (counterLock) { counter2 = counter2 + 1; System.out.println( Thread.currentThread().getName() +"\t\t « InstanceVolatile :: "+ counter2); } } public void concurrentMethodWrong() { counter = counter + 1; System.out.println( Thread.currentThread().getName() +" « Static :: "+ counter); sleepThread( 1/4 ); counter1 = counter1 + 1; System.out.println( Thread.currentThread().getName() +"\t « StaticVolatile :: "+ counter1); sleepThread( 1/4 ); addInstanceVolatile(); sleepThread( 1/4 ); counter3 = counter3 + 1; sleepThread( 1/4 ); System.out.println( Thread.currentThread().getName() +"\t\t\t\t\t « Instance :: "+ counter3); } public static void main(String[] args) throws InterruptedException { Runtime runtime = Runtime.getRuntime(); int availableProcessors = runtime.availableProcessors(); System.out.println("availableProcessors :: "+availableProcessors); System.out.println("MAX JVM will attempt to use : "+ runtime.maxMemory() / MegaBytes ); System.out.println("JVM totalMemory also equals to initial heap size of JVM : "+ runtime.totalMemory() / MegaBytes ); System.out.println("Returns the amount of free memory in the JVM : "+ untime.freeMemory() / MegaBytes ); System.out.println(" ===== ----- ===== "); VolatileTest volatileTest = new VolatileTest(); Thread t1 = new Thread( volatileTest ); t1.start(); Thread t2 = new Thread( volatileTest ); t2.start(); Thread t3 = new Thread( volatileTest ); t3.start(); Thread t4 = new Thread( volatileTest ); t4.start(); Thread.sleep( 10 );; Thread optimizeation = new Thread() { @Override public void run() { System.out.println("Thread Start."); Integer appendingVal = volatileTest.counter2 + volatileTest.counter2 + volatileTest.counter2; System.out.println("End of Thread." + appendingVal); } }; optimizeation.start(); } public void sleepThread( long sec ) { try { Thread.sleep( sec * 1000 ); } catch (InterruptedException e) { e.printStackTrace(); } } } 

Static [ Class Field ] vs Volátil [ Instance Field ]: no se almacenan en caché por subprocesos

  • Los campos estáticos son comunes a todos los hilos y se almacenan en el Área del método. Estático con uso no volátil. El campo estático no se puede serializar.

  • Volátil principalmente utilizado con variables de instancia que se almacenan en el área de montón. El uso principal de volátiles es mantener el valor actualizado sobre todos los subprocesos. el campo volátil de la instancia se puede serializar .

@ver

  • Volatile Vs Static en java
  • Sistema con múltiples núcleos que comparten un caché L2
  • Modelo de memoria JVM

volatile no va a crear necesariamente cambios gigantescos, según la JVM y el comstackdor. Sin embargo, para muchos casos (de borde), puede ser la diferencia entre la optimización que causa que los cambios de una variable no se noten en lugar de que se escriban correctamente.

Básicamente, un optimizador puede elegir colocar variables no volátiles en los registros o en la stack. Si otro subproceso los cambia en el montón o en las primitivas de las clases, el otro subproceso seguirá buscándolo en la stack, y estará obsoleto.

volatile asegura que tales optimizaciones no suceden y todas las lecturas y escrituras son directamente al montón u otro lugar donde todos los hilos lo verán.

Encuentre la solución a continuación,

El valor de esta variable nunca se almacenará en caché localmente: todas las lecturas y escrituras irán directamente a la “memoria principal”. La fuerza volátil del hilo para actualizar la variable original para cada momento.

 public class VolatileDemo { private static volatile int MY_INT = 0; public static void main(String[] args) { ChangeMaker changeMaker = new ChangeMaker(); changeMaker.start(); ChangeListener changeListener = new ChangeListener(); changeListener.start(); } static class ChangeMaker extends Thread { @Override public void run() { while (MY_INT < 5){ System.out.println("Incrementing MY_INT "+ ++MY_INT); try{ Thread.sleep(1000); }catch(InterruptedException exception) { exception.printStackTrace(); } } } } static class ChangeListener extends Thread { int local_value = MY_INT; @Override public void run() { while ( MY_INT < 5){ if( local_value!= MY_INT){ System.out.println("Got Change for MY_INT "+ MY_INT); local_value = MY_INT; } } } } } 

Consulte este enlace http://java.dzone.com/articles/java-volatile-keyword-0 para obtener más claridad.

si declara una variable como volátil, no se almacenará en la memoria caché local. Cada vez que el hilo está actualizando los valores, se actualiza a la memoria principal. Por lo tanto, otros subprocesos pueden acceder al valor actualizado.

Idealmente, si keepRunning no era volátil, el hilo debería seguir ejecutándose indefinidamente. Pero, se detiene después de unos segundos.

Si está ejecutando en un solo procesador o si su sistema está muy ocupado, el sistema operativo puede estar intercambiando los hilos que causa algunos niveles de invalidación de caché. Como han mencionado otros, no tener un elemento volatile no significa que la memoria no se compartirá, pero la JVM intenta no sincronizar la memoria si puede por motivos de rendimiento, por lo que la memoria puede no actualizarse.

Otra cosa a tener en cuenta es que System.out.println(...) se sincroniza porque el PrintStream subyacente realiza la sincronización para detener la superposición de resultados. Por lo tanto, obtiene sincronización de memoria “gratis” en el hilo principal. Sin embargo, esto todavía no explica por qué el ciclo de lectura ve las actualizaciones.

Ya sea que las println(...) estén dentro o fuera, su progtwig gira para mí bajo Java6 en una MacBook Pro con Intel i7.

¿Alguien puede explicar volátil con el ejemplo? No con la teoría de JLS.

Creo que tu ejemplo es bueno. No estoy seguro de por qué no funciona con todas las sentencias System.out.println(...) eliminadas. Esto funciona para mi.

¿Es un sustituto volátil para la sincronización? ¿Logra la atomicidad?

En términos de sincronización de memoria, volatile lanza las mismas barreras de memoria que un bloque synchronized excepto que la barrera volatile es unidireccional versus bidireccional. volatile lecturas volatile arrojan una barrera de carga mientras que las escrituras arrojan una barrera de la tienda. Un bloque synchronized es una barrera bidireccional.

En términos de atomicity , sin embargo, la respuesta es “depende”. Si está leyendo o escribiendo un valor de un campo, entonces volatile proporciona la atomicidad adecuada. Sin embargo, el incremento de un campo volatile adolece de la limitación de que ++ es en realidad 3 operaciones: leer, incrementar, escribir. En ese caso o casos más complejos de mutex, un locking synchronized completo está en orden.

Los objetos que se declaran como volátiles generalmente se utilizan para comunicar información de estado entre subprocesos, para garantizar que las cachés de CPU se actualicen, es decir, se mantengan sincronizadas, en presencia de campos volátiles, una instrucción de CPU, una barrera de memoria, a menudo denominada membar o valla, se emite para actualizar las memorias caché de la CPU con un cambio en el valor de un campo volátil.

El modificador volátil le dice al comstackdor que la variable modificada por el volátil puede cambiarse inesperadamente por otras partes de su progtwig.

La variable volátil se debe usar solo en el contexto de subprocesos. mira el ejemplo aquí