2011-04-28 10 views
13

Actualmente no puedo entender cuándo debemos usar volatile para declarar la variable.El ejemplo del código que puede probar que declara "volátil" debe usarse

Realicé un estudio y busqué algunos materiales al respecto durante mucho tiempo y sé que cuando un campo se declara volátil, el compilador y el tiempo de ejecución reciben aviso de que esta variable se comparte y que las operaciones en ella no deben ser reordenado con otras operaciones de memoria.

Sin embargo, todavía no puedo entender en qué situación deberíamos usarlo. Quiero decir, ¿puede alguien proporcionar algún código de ejemplo que pueda demostrar que usar "volátil" trae beneficios o resolver problemas en comparación con sin usarlo?

+6

'volatile' (y muchos otros aspectos relacionados con la concurrencia) están ** ** muy duro demostrar, porque si los usa incorrectamente, entonces * todavía * pueden parecer que funcionan correctamente y no muestran un problema hasta que ocurre una condición muy específica y ocasiona un problema. –

+0

He intentado esto por mí mismo y he producido una prueba que pasa los valores entre las hebras utilizando un campo no volátil mil millones de veces con éxito, sin embargo, falla relativamente rápido cuando se carga la misma caja. Incluso medir el impacto en el rendimiento de los volátiles es muy difícil, depende de muchos factores. –

Respuesta

12

Aquí hay un ejemplo de por qué volatile es necesario. Si elimina la palabra clave volatile, el hilo 1 puede nunca terminar. (Cuando probé en Java 1.6 Hotspot en Linux, este fue el caso - los resultados pueden variar a medida que la JVM no está obligado a hacer ningún almacenamiento en caché de variables que no estén marcadas volatile.)

public class ThreadTest { 
    volatile boolean running = true; 

    public void test() { 
    new Thread(new Runnable() { 
     public void run() { 
     int counter = 0; 
     while (running) { 
      counter++; 
     } 
     System.out.println("Thread 1 finished. Counted up to " + counter); 
     } 
    }).start(); 
    new Thread(new Runnable() { 
     public void run() { 
     // Sleep for a bit so that thread 1 has a chance to start 
     try { 
      Thread.sleep(100); 
     } catch (InterruptedException ignored) { } 
     System.out.println("Thread 2 finishing"); 
     running = false; 
     } 
    }).start(); 
    } 

    public static void main(String[] args) { 
    new ThreadTest().test(); 
    } 
} 
+0

Lo siento, traté de eliminar la palabra clave "volátil" y ejecutar el programa muchas veces, pero cada vez que se puede finalizar la secuencia 1. Creo que para el código que proporcionaste, incluso sin el hilo "volátil" 1, siempre se puede terminar cada vez. –

+3

@Eric: da el efecto que describo en dos máquinas diferentes que ejecutan Java 6 JVM. Creo que debería haberlo expresado de otra manera.Solo se garantiza que terminará si la palabra clave volátil está presente. Si no está presente, la JVM tiene libertad para almacenar en caché el valor de 'running', por lo que no es necesario que el hilo 1 finalice. Sin embargo, no está obligado a almacenar en caché el valor. –

+0

Mantener "volátil" presente terminará el subproceso 1 tan pronto como 'ejecutando' se establezca como falso en el subproceso 2. Pero si elimina "volátil", el subproceso 1 no terminará incluso después de que 'ejecución' se establezca como falso en thread2, cuando subproceso terminará depende de cuándo JVM actualiza el valor de "ejecución" en el hilo 1. ¿Quiere decir eso? Pero si "ejecutar" se ha configurado como "falso" en el hilo 2, ¿por qué thread1 no puede obtener el valor correcto? Si es así, creo que JVM se comporta mal. –

1

La palabra clave volátil es bastante compleja y debe comprender lo que hace y lo que no funciona bien antes de usarla. Recomiendo leer this language specification section que lo explica muy bien.

Destacan este ejemplo:

class Test { 
    static volatile int i = 0, j = 0; 
    static void one() { i++; j++; } 
    static void two() { 
     System.out.println("i=" + i + " j=" + j); 
    } 
} 

Lo que esto significa es que durante one()j nunca es mayor que i. Sin embargo, otro subproceso que ejecuta two() puede imprimir un valor de j que es mucho mayor que i porque digamos que two() se está ejecutando y obtiene el valor de i. Luego one() se ejecuta 1000 veces. Luego, el hilo que ejecuta dos finalmente se programa de nuevo y recoge j que ahora es mucho más grande que el valor de i. Creo que este ejemplo demuestra perfectamente la diferencia entre volátil y sincronizado: las actualizaciones a i y j son volátiles, lo que significa que el orden en que ocurren es consistente con el código fuente. Sin embargo, las dos actualizaciones se producen por separado y no de forma atómica, por lo que las personas que llaman pueden ver valores que se ven (para esa persona que llama) como inconsistentes.

En pocas palabras: ¡tenga mucho cuidado con volatile!

+0

Desde el enlace que proporciona, un ejemplo a continuación: class Test { \t static volatile int i = 0, j = 0; \t static void one() {i ++; j ++; } \t static void two() { \t \t System.out.println ("i =" + i + "j =" + j); } "Esto permite que el método uno y el método dos se ejecuten simultáneamente, pero garantiza que los accesos a los valores compartidos para i y j ocurren exactamente tantas veces, y exactamente en el mismo orden, como aparecen durante ejecución del texto del programa por cada hilo "Pero no creo que el ejemplo sea correcto, usar" volátil "o no tiene el mismo resultado, j siempre tiene la posibilidad de ser diferente de i. –

+0

@Eric Jiang - El punto es j puede ser __greater__ que yo. Aunque siempre estoy incrementado primero. Si ambos son volátiles, aún pueden ser diferentes, pero j no puede ser mayor que yo. – Ishtar

+0

He actualizado mi respuesta para explicar lo que creo que dice el JLS (aunque creo que el JLS lo dijo muy bien en primer lugar) :) – alpian

2

Eric, he leído sus comentarios y uno en particular me sorprende

De hecho, puedo entender el uso de volátiles en el concepto nivel. Sin embargo, para la práctica, no se me ocurre el código que tiene la concurrencia problemas sin utilizar volátil

El problema obvio que puede tener son reordenamientos del compilador, por ejemplo, la más famosa de elevación como se ha mencionado por Simon Nickerson. Pero supongamos que no habrá reordenamientos, ese comentario puede ser válido.

Otro problema que resuelve volátil es con variables de 64 bits (largo, doble). Si escribe en una larga o doble, se trata como dos tiendas separadas de 32 bits. Lo que puede suceder con una escritura simultánea es que el alto 32 de un hilo se escribe en los 32 bits altos del registro, mientras que otro hilo escribe los 32 bits bajos.Entonces puede tener un largo que no sea ni uno ni el otro.

Además, si observa la sección de memoria del JLS, observará que se trata de un modelo de memoria relajado.

Eso significa que las escrituras pueden no ser visibles (pueden estar en el búfer de la tienda) por un tiempo. Esto puede llevar a lecturas obsoletas. Ahora puede decir que parece poco probable, y lo es, pero su programa es incorrecto y tiene el potencial de fallar.

Si tiene un int que está incrementando durante la vida útil de una aplicación y sabe (o al menos piensa) que el desbordamiento de la información no se ampliará, no podrá actualizarlo a una extensión larga, pero aún es posible. . En el caso de un problema de visibilidad de la memoria, si cree que no debería afectarlo, debe saber que todavía puede y puede causar errores en su aplicación simultánea que son extremadamente difíciles de identificar. La corrección es la razón para usar volátil.

3

El siguiente es un ejemplo canónico de la necesidad de volátil (en este caso para la variable str. Sin ella, hotspot levanta el acceso fuera del bucle (while (str == null)) y run() nunca se termina. Esto ocurrirá en la mayoría de las JVM -server .

public class DelayWrite implements Runnable { 
    private String str; 
    void setStr(String str) {this.str = str;} 

    public void run() { 
      while (str == null); 
      System.out.println(str); 
    } 

    public static void main(String[] args) { 
      DelayWrite delay = new DelayWrite(); 
      new Thread(delay).start(); 
      Thread.sleep(1000); 
      delay.setStr("Hello world!!"); 
    } 
} 
+0

Wesley: ¡Muchas gracias! Puedo ver el problema con java -server para iniciar JVM y ejecutar la aplicación. Ahora recibo mi respuesta, ¡gracias! –

+0

nuevo Thread (fun) .start(); esta línea debería ser nueva Thread (delay) .start(); correcto? –

+0

sí, arreglado. Gracias. –

0

Un ejemplo minimalista en java 8, si se quita la palabra clave volátil que nunca se terminará.

public class VolatileExample { 

    private static volatile boolean BOOL = true; 

    public static void main(String[] args) throws InterruptedException { 
     new Thread(() -> { while (BOOL) { } }).start(); 
     TimeUnit.MILLISECONDS.sleep(500); 
     BOOL = false; 
    } 
} 
Cuestiones relacionadas