2009-12-14 10 views
6

Estoy tratando de averiguar si el código de abajo adolece de posibles problemas de simultaneidad. Específicamente, la cuestión de la visibilidad relacionada con las variables volátiles. volátil se define como: El valor de esta variable no se almacenan en caché hilos a nivel local: todas las lecturas y escrituras irá directamente a "memoria principal"Concurrencia, visibilidad del objeto

public static void main(String [] args) 
{ 
    Test test = new Test(); 

    // This will always single threaded 
    ExecutorService ex = Executors.newSingleThreadExecutor(); 

    for (int i=0; i<10; ++i) 
     ex.execute(test); 
} 

private static class Test implements Runnable { 
    // non volatile variable in question 
    private int state = 0; 

    @Override 
    public void run() { 
     // will we always see updated state value? Will updating state value 
     // guarantee future run's see the value? 
     if (this.state != -1) 
      this.state++; 
    } 
} 

Por lo anterior único ejecutor roscado:

¿Está bien hacer test.state no volátil? En otras palabras, cada Test.run() sucesivo (que ocurrirá secuencialmente y no concurrentemente porque de nuevo el ejecutor tiene un solo subproceso), siempre vea el valor test.state actualizado? De lo contrario, no sale de Test.run() para asegurarse de que cualquier cambio realizado en el hilo localmente se vuelva a escribir en la memoria principal? De lo contrario, ¿cuándo se vuelven a escribir en la memoria principal los cambios hechos localmente, si no al salir del hilo?

+0

dónde sacaste esa definición. Suena como una definición JMM pre-1.5 (que no era implementable). –

+0

http://www.javamex.com/tutorials/synchronization_volatile.shtml – Integer

+0

Es importante darse cuenta de que cuando el hilo completa 'Test.run()', el hilo no termina, y cualquier garantía sobre los valores escritos por un el hilo que se va a enjuagar a la memoria principal antes de que termine no se aplica.El subproceso de método 'run()' que invoca su 'Test.run()' es simplemente un bucle, que bloquea hasta que recibe una nueva tarea para ejecutar. Cuando esa tarea regresa del método * its * 'run()', el hilo bloquea hasta la siguiente tarea; no termina (y por lo tanto, vacia su estado). – erickson

Respuesta

2

Sí, es seguro, incluso si el ejecutor reemplazó su hilo en el medio. Thread start/terminte también son puntos de sincronización.

http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.4.4

Un ejemplo simple:

static int state; 
static public void main(String... args) { 
    state = 0;     // (1) 
    Thread t = new Thread() { 
     public void run() { 
      state = state + 1; // (2) 
     } 
    }; 
    t.start(); 
    t.join(); 
    System.out.println(state); // (3) 
} 

Se garantiza que (1), (2), (3) están bien ordenado y se comportan como se esperaba.

Para el ejecutor solo hilo, "Tareas están garantizados para ejecutar secuencialmente", se debe detectar alguna manera el acabado de una tarea antes de comenzar el siguiente, que sincroniza necesariamente adecuadamente los diferentes run() 's

-1

Si su ExecutorService tiene una sola hebra, entonces no hay un estado compartido, por lo que no veo cómo podría haber problemas al respecto.

Sin embargo, ¿no tendría más sentido pasar una nueva instancia de su clase Test a cada llamada al execute()? es decir

for (int i=0; i<10; ++i) 
    ex.execute(new Test()); 

De esta manera no habrá ningún estado compartido.

+1

Eso no tiene ningún sentido en absoluto, el punto central de la pregunta es que se usa el mismo objeto. – KernelJ

4

Siempre y cuando se trate de un solo hilo, no es necesario hacerlo volátil. Si vas a usar múltiples hilos, no solo deberías usar volátiles sino también sincronizarlos. Incrementar un número no es una operación atómica - esa es una idea errónea común.

public void run() { 
    synchronize (this) { 
     if (this.state != -1) 
      this.state++; 
    } 
} 

En lugar de utilizar la sincronización, también se puede utilizar AtomicInteger#getAndIncrement() (si no se necesita un si antes).

private AtomicInteger state = new AtomicInteger(); 

public void run() { 
    state.getAndIncrement() 
} 
+0

Solo me preguntaba si los cambios de estado se verían en otros subprocesos en un único modo de subproceso y usted respondió eso. Sin embargo, saliendo del tema, no veo por qué necesito usar VOLÁTILES Y sincronizar. Me parece que la sincronización sería suficiente. – Integer

+0

Solo quería hacer que (y otros tropezaran con esta pregunta) conscientes del hecho de que usar volátiles para incrementar un entero no es suficiente en un entorno de subprocesos múltiples. Siempre necesitará ambos, volátil y sincronizado para ser seguro para subprocesos. – sfussenegger

+0

La razón por la cual la volatilidad a menudo no es suficiente es clara para mí: dado que la no atomicidad de ++ algunos de los incrementos de los hilos pueden no tener un efecto debido a las condiciones de carrera. Pero ¿por qué la sincronización sin volatilidad no debería ser suficiente? Si sincronizas las lecturas también, por supuesto. –

0

Su código, específicamente este bit

  if (this.state != -1) 
        this.state++; 

requeriría la atómica de prueba del valor de estado y luego un incremento al estado en un contexto concurrente. Así que incluso si su variable era volátil y se trataba de más de un hilo, tendría problemas de concurrencia.

Pero su diseño se basa en afirmar que siempre habrá solo una instancia de Prueba, y, esa única instancia solo se concede a una única (misma) cadena de caracteres. (Pero tenga en cuenta que la instancia única es, de hecho, un estado compartido entre el hilo principal y el hilo del ejecutor.)

Creo que necesita hacer estas suposiciones más explícitas (en el código, por ejemplo, use ThreadLocal y ThreadLocal .obtener()). Esto es para proteger tanto contra errores futuros (cuando algún otro desarrollador puede violar descuidadamente las suposiciones de diseño) como para evitar asumir la implementación interna de the Executor method you are using que en algunas implementaciones simplemente proporciona un ejecutor de un solo subproceso (es decir,secuencial y no necesariamente el mismo subproceso en cada invocación de ejecución (ejecutable).

+0

Escucha, sé que el código no es una obra de arte. Entiendo que hay millones de mejoras que se pueden hacer. Sí, sé sobre threadlocals, sé sobre sincronización y variables atómicas. No es código de producción. Está hecho código para ilustrar mi pregunta de visibilidad concurrente y volátil. – Integer

0

Está perfectamente bien que el estado no sea volátil en este código específico, porque solo hay un hilo y solo ese hilo accede al campo. Al deshabilitar el almacenamiento en caché del valor de este campo dentro del único hilo que tienes, solo obtendrás un golpe de rendimiento.

Sin embargo, si desea utilizar el valor de estado en el hilo principal que se ejecuta el bucle, que tiene que hacer el campo volátil:

for (int i=0; i<10; ++i) { 
      ex.execute(test); 
      System.out.println(test.getState()); 
    } 

Sin embargo, incluso esto no podría funcionar correctamente con volátil, porque no hay sincronización entre los hilos.

Dado que el campo es privado, solo existe un problema si el hilo principal ejecuta un método que puede acceder a este campo.

3

Originalmente, estaba pensando de esta manera:

Si la tarea siempre fueron ejecutados por el mismo hilo, no habría ningún problema . Pero Excecutor producido por newSingleThreadExecutor() puede crear nuevos hilos para reemplazar aquellos que se maten por cualquier motivo. No hay garantía sobre cuándo se creará el hilo de reemplazo o qué hilo lo creará.

Si un hilo realiza algunas escrituras, entonces llama start() en un nuevo hilo, esos escrituras serán visibles para el nuevo hilo. Pero no hay garantía de que esa regla se aplique en este caso.

Pero irrecutable es correcto: la creación de una correcta ExecutorService sin suficientes barreras para garantizar la visibilidad es prácticamente imposible. Estaba olvidando que la detección de la muerte de otro hilo es una relación sincroniza con. El mecanismo de bloqueo utilizado para ralentizar los hilos de los trabajadores también requeriría una barrera.

+0

"No hay garantía sobre cuándo se creará el hilo de reemplazo o qué hilo lo creará". Si ejecutor ejecuta Prueba en el mismo subproceso o subproceso, ¿hace una diferencia? Porque estoy usando la misma instancia de Test(). Mi pregunta es específica sobre si todas las ejecuciones de Test.run() verán el valor de estado previamente actualizado. – Integer

+2

Sí, si el mismo hilo se usa o no para ejecutar 'Test' marca la diferencia. Si se garantiza que un único subproceso ejecutará todas las ejecuciones de 'run()', entonces se trata de una programación normal de un solo subproceso, y las actualizaciones se verán ciertamente en las invocaciones posteriores de ese subproceso. Sin embargo, dado que la tarea no utiliza barreras de memoria, esas actualizaciones pueden no ser visibles para una cadena * diferente *, incluida una cadena de reemplazo creada por el ejecutor. – erickson

+0

Gracias. Usted respondió mi pregunta perfectamente. – Integer

Cuestiones relacionadas