2009-12-17 18 views
18

Estoy viendo un ejemplo de código de "Java Concurrency in Practice" de Brian Goetz. Él dice que es posible que este código permanezca en un bucle infinito porque "el valor de 'listo' podría no volverse visible para el hilo del lector". No entiendo cómo puede ocurrir esto ...pregunta sobre "Concurrencia de Java en la práctica" ejemplo

public class NoVisibility { 
    private static boolean ready; 
    private static int number; 

    private static class ReaderThread extends Thread { 
     public void run() { 
      while (!ready) 
       Thread.yield(); 
      System.out.println(number); 
     } 
    } 

    public static void main(String[] args) { 
     new ReaderThread().start(); 
     number = 42; 
     ready = true; 
    } 
} 

Respuesta

27

Debido ready no está marcado como volatile y el valor puede tener una caché en el inicio del bucle while porque no se cambia dentro del bucle while . Es una de las formas en que el jitter optimiza el código.

Por lo tanto, es posible que el subproceso comience antes del ready = true y lea ready = false cachés localmente y nunca vuelva a leerlo.

Echa un vistazo the volatile keyword.

+0

Si 'listo' es volátil pero el número no es, ¿es posible que el número se imprima como 0? – zhiyuany

+0

Hola Qberticus, esto es sólo una aclaración, si el hilo principal se completa antes del hilo del lector, entonces tenemos la oportunidad de imprimir el número 42, ¿verdad? –

8

El motivo se explica en la sección siguiente al que muestra el código.

3.1.1 Los datos obsoletos

NoVisibility demostrado en las formas en que los programas insuficientemente sincronizados pueden causar que los resultados sorprendentes: datos obsoletos. Cuando el hilo del lector examina ready, es posible que vea un valor desactualizado. A menos que se use la sincronización cada vez que se accede a una variable, es posible ver un valor obsoleto para esa variable.

6

el modelo de memoria de Java permite la JVM para optimizar referencia accesos y, como si se trata de una única aplicación roscada, a menos que el campo está marcado como volatile o los accesos con una cerradura que se celebra (la historia se complica un poco con bloqueos en realidad).

En el ejemplo, que ya ha proporcionado, la JVM podría inferir que ready campo no puede ser modificada dentro del hilo actual, por lo que sería reemplazar !ready con false, provocando un bucle infinito. Al marcar el campo como volatile, la JVM comprobará el valor del campo cada vez (o al menos garantizará que los cambios ready se propaguen a la secuencia en ejecución).

+0

Solo un poco curioso, ¿por qué JVM será tan descuidado al reemplazar '! Ready' con' false'? Después de todo 'listo' no es un campo dentro de' ReaderThread'. – sleepsort

+0

Porque a menos que se marque como volátil, la JVM no tiene obligación de volver a leerla de acuerdo con el Modelo de memoria de Java. Esta optimización (elevación variable) es bastante común. – assylias

2
private static boolean ready; 
private static int number; 

La forma en que el modelo de memoria puede funcionar es que cada hilo se podía leer y escribir en su propia copia de estas variables (el problema afecta a las variables miembro no estáticas también). Esto es una consecuencia de la forma en que la arquitectura subyacente puede funcionar.

Jeremy Manson and Brian Goetz:

En los sistemas multiprocesador, los procesadores tienen generalmente una o más capas de caché de la memoria, que mejora el rendimiento tanto por exceso de velocidad de acceso a los datos (porque los datos está más cerca del procesador) y reduciendo el tráfico en el bus de memoria compartida (porque muchas operaciones de memoria pueden ser satisfechas por cachés locales.) Las memorias caché de memoria pueden mejorar enormemente el rendimiento, pero presentan una serie de desafíos nuevos. ¿Qué ocurre, por ejemplo, cuando dos procesadores examinan la misma ubicación de memoria al mismo tiempo? ¿Bajo qué condiciones verán el mismo valor?

Así, en su ejemplo, los dos hilos podrían ejecutarse en procesadores diferentes, cada uno con una copia de ready en sus propios, cachés separadas. El lenguaje Java proporciona los mecanismos volatile y synchronized para garantizar que los valores vistos por los hilos estén sincronizados.

+0

Gracias a todos, tiene sentido ahora. Así que esto es específico de la forma en que las JVM asignan el código de Java en código ejecutable. ¿Asumo que preocupaciones similares pueden surgir en C++? ¿Alguien sabe cómo C++ maneja este problema? –

4

El problema radica en el hardware: cada CPU tiene un comportamiento diferente con respecto a la coherencia del caché, la visibilidad de la memoria y el reordenamiento de las operaciones. Java está en mejor forma aquí que C++ porque define un modelo de memoria multiplataforma con el que todos los programadores pueden contar. Cuando Java se ejecuta en un sistema cuyo modelo de memoria es más débil que el requerido por el Modelo de memoria de Java, la JVM tiene que compensar la diferencia.

Idiomas como C "heredan" el modelo de memoria del hardware subyacente. Hay un trabajo en marcha para darle a C++ un modelo formal de memoria para que los programas C++ puedan significar lo mismo en diferentes plataformas.

+0

No tengo idea si este es el verdadero brian goetz, pero para realmente llevar este punto a casa, me gustaría ver un ejemplo que realmente falla. Probé el código de muestra y no puedo hacer que falle, incluso después de muchas ejecuciones. ¿Algún consejo? – jbu

2
public class NoVisibility { 

    private static boolean ready = false; 
    private static int number; 

    private static class ReaderThread extends Thread { 

     @Override 
     public void run() { 
      while (!ready) { 
       Thread.yield(); 
      } 
      System.out.println(number); 
     } 
    } 

    public static void main(String[] args) throws InterruptedException { 
     new ReaderThread().start(); 
     number = 42; 
     Thread.sleep(20000); 
     ready = true; 
    } 
} 

Ponga la llamada Thread.sleep() durante 20 segundos lo que va a pasar es JIT entrará en funcionamiento durante esos 20 segundos y se optimizará el cheque y almacenar en caché el valor o eliminar por completo la condición. Y entonces el código fallará en la visibilidad.

Para evitar que esto ocurra DEBE usar volatile.

+0

esto no parece * fallar * después de decenas de intentos. usando Java 1.8 – jbu

Cuestiones relacionadas