2012-06-12 11 views
14

¿Deberíamos declarar los campos privados como volatile si las instancias se usan en varios hilos?java: campos privados `volátiles` con getters y setters

En Effective Java, no es un ejemplo en el que el código no funciona sin volátil:

import java.util.concurrent.TimeUnit; 

// Broken! - How long would you expect this program to run? 
public class StopThread { 
    private static boolean stopRequested; // works, if volatile is here 

    public static void main(String[] args) throws InterruptedException { 
     Thread backgroundThread = new Thread(new Runnable() { 
      public void run() { 
       int i = 0; 
       while (!stopRequested) 
        i++; 
      } 
     }); 
     backgroundThread.start(); 
     TimeUnit.SECONDS.sleep(1); 
     stopRequested = true; 
    } 
} 

Las explicaciones dice que

while(!stopRequested) 
    i++; 

está optimizado para algo como esto:

if(!stopRequested) 
    while(true) 
     i++; 

por lo que no se ven más modificaciones de stopRequested por el hilo de fondo, por lo que se repite para siempre. (Por cierto, que el código termina sin volatile en JRE7.)

Consideremos ahora esta clase:

public class Bean { 
    private boolean field = true; 

    public boolean getField() { 
     return field; 
    } 

    public void setField(boolean value) { 
     field = value; 
    } 
} 

y un hilo de la siguiente manera:

public class Worker implements Runnable { 
    private Bean b; 

    public Worker(Bean b) { 
     this.b = b; 
    } 

    @Override 
    public void run() { 
     while(b.getField()) { 
      System.err.println("Waiting..."); 
      try { Thread.sleep(1000); } 
      catch(InterruptedException ie) { return; } 
     } 
    } 
} 

El código anterior funciona como se espera sin usar sustancias volátiles :

public class VolatileTest { 
    public static void main(String [] args) throws Exception { 
     Bean b = new Bean(); 

     Thread t = new Thread(new Worker(b)); 
     t.start(); 
     Thread.sleep(3000); 

     b.setField(false); // stops the child thread 
     System.err.println("Waiting the child thread to quit"); 
     t.join(); 
     // if the code gets, here the child thread is stopped 
     // and it really gets, with JRE7, 6 with -server, -client 
    } 
} 

Creo que debido a la configuración pública, la c ompiler/JVM nunca debe optimizar el código que llama al getField(), pero this article dice que hay un patrón de "Bean volátil" (Patrón # 4), que se debe aplicar para crear clases mutables de seguridad de subprocesos. Actualización: tal vez ese artículo se aplica solo a IBM JVM?

La pregunta es: ¿qué parte de JLS explícita o implícitamente dice que los campos primitivos privados con getters/setters públicos deben declararse como volatile (o no tienen que hacerlo)?

Lo siento por una pregunta larga, traté de explicar el problema en detalle. Avísame si algo no está claro. Gracias.

+0

No es que necesite ese campo para cancelar el hilo, podría usar el indicador interrumpido en el hilo. –

+1

@NathanHughes, estas clases son solo ejemplos mínimos, el código real es diferente, no se necesita ninguna interrupción de hilo allí. – khachik

Respuesta

4

Antes de responder a la pregunta que quiero tratar

Por cierto, que el código termina sin volátil en JRE7

Esto puede cambiar si se va a implementar la misma aplicación con diferentes argumentos de tiempo de ejecución. La elevación no es necesariamente una implementación predeterminada para JVM, por lo que puede funcionar en una y no en otra.

Para responder a su pregunta no hay nada que impida que el compilador Java desde la ejecución de su último ejemplo al igual que

@Override 
public void run() { 
    if(b.getField()){ 
     while(true) { 
      System.err.println("Waiting..."); 
      try { Thread.sleep(1000); } 
      catch(InterruptedException ie) { return; } 
     } 
    } 
} 

Es todavía secuencialmente consistente y por lo tanto mantiene garantías de Java - se puede leer específicamente 17.4.3:

Entre todas las acciones entre subprocesos realizadas por cada subproceso t, el orden del programa de t es un orden total que refleja el orden en el que estas acciones se realizarían de acuerdo con la semántica intra-thread de t.

Un conjunto de acciones es secuencialmente consistente si todas las acciones se producen en un orden total (el orden de ejecución) que es consistente con el orden del programa , y además, cada uno leer r de una variable v ve el valor escrito por el escribir w a v tal que:

en otras palabras - siempre que un hilo verá de lectura y escritura de un campo en el mismo orden, independientemente del compilador/memoria re ordenando se considera secuencialmente consistente.

9

La pregunta es: ¿qué parte de JLS explícita o implícitamente dice que los campos primitivos privados con getters/setters públicos deben declararse como volátiles (o no tienen que hacerlo)?

El modelo de memoria JLS no se preocupa por getters/setters. No son operaciones desde la perspectiva del modelo de memoria; también podría acceder a los campos públicos. Envolver el booleano detrás de una llamada de método no afecta la visibilidad de su memoria. Tu último ejemplo funciona puramente por suerte.

¿Deberíamos declarar los campos privados como volátiles si las instancias se usan en varios subprocesos?

Si una clase (frijol) se va a utilizar en el entorno multiproceso, debe de alguna manera tener eso en cuenta. Hacer los campos privados volatile es un enfoque: garantiza que se garantice que cada subproceso vea el último valor de ese campo, no que se almacenen en caché/optimicen los valores obsoletos. Pero no resuelve el problema de atomicity.

The article you linked to se aplica a cualquier JVM que se adhiera a la especificación JVM (que se apoya en JLS). Obtendrá diversos resultados dependiendo del proveedor de JVM, la versión, los indicadores, la computadora y el sistema operativo, la cantidad de veces que ejecuta el programa (las optimizaciones de HotSpot suelen aparecer después de la 10000ª ejecución), etc., así que realmente debe comprender las especificaciones y adherirse con cuidado a las reglas para crear programas confiables. Experimentar en este caso es una manera pobre de descubrir cómo funcionan las cosas porque la JVM puede comportarse de la manera que quiera, siempre y cuando esté dentro de la especificación, y la mayoría de las JVM contienen cargas de todo tipo de optimizaciones dinámicas.

+0

gracias. Establecer una variable booleana (o cualquier otra variable primitiva excepto larga/doble) es una operación atómica. Mi pregunta es acerca de cómo el JSL define el comportamiento del código en este caso. O cómo no define, si está claro lo que quiero decir. – khachik

+0

El JLS no define nada particular en su caso. Un bean es simplemente variables detrás de las llamadas a métodos, el [modelo de memoria] (http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4) se aplica tal como está. Las llamadas a métodos no son operaciones desde la perspectiva del modelo de memoria. Por atomicidad quiero decir que su bean podría terminar en un estado inconsistente, si tiene campos que lógicamente dependen el uno del otro: por ejemplo, el tiempo de "inicio" debe ser anterior al tiempo "final" o algo así, no está garantizado a menos que tenga un mecanismo para cambiar esos dos campos atómicamente. –

+0

Por cierto, las escrituras y lecturas de los valores 'volátiles'' long' y' double' son siempre atómicas. Ver JLS 17.7. –

4

No, ese código es igual de incorrecto. Nada en el JLS dice que un campo debe ser declarado como volátil. Sin embargo, si desea que su código funcione correctamente en un entorno de subprocesos múltiples, debe obedecer las reglas de visibilidad. volátiles y sincronizados son dos de las principales facilidades para hacer correctamente visibles los datos a través de los hilos.

En cuanto a su ejemplo, la dificultad de escribir código multiproceso es que muchas formas de código incorrecto funcionan bien en las pruebas. El hecho de que una prueba de múltiples subprocesos "tenga éxito" en las pruebas no significa que sea el código correcto.

Para obtener la referencia JLS específica, consulte la sección Happens Before (y el resto de la página).

Tenga en cuenta que, como regla general, si cree que se le ha ocurrido una nueva forma inteligente de evitar modismos "estándar" seguros para la ejecución de subprocesos, lo más probable es que esté equivocado.

+0

si nada en el JSL dice que el campo debe declararse como volátil, ¿cómo se sabe que debe declararse? como volátil? Su opinión sobre la prueba del código mutli-threaded está más que bien, pero la pregunta era sobre el JSL. – khachik

+0

@khachik: el JLS proporciona detalles específicos de lo que debe hacer para que su código sea seguro para subprocesos. hay una variedad de opciones en la sección a la que me refiero. no hay nada en el JLS que le requiera que haga que su código sea seguro para subprocesos, por lo tanto no es necesario que el campo sea volátil (de lo contrario, todos los campos serían volátiles por defecto). – jtahlborn

+0

seguro. No responde mi pregunta. – khachik