termino - multihilos java
¿Cómo demostrar los problemas de visibilidad de multiprocesamiento de Java? (7)
Si se accede a las variables en Java desde múltiples hilos, se debe asegurar que estén publicadas de forma segura. Esto generalmente significa usar synchronized
o volatile
.
Tengo la impresión de que algunos de mis colegas no toman en serio este asunto, ya que "nunca antes habían escuchado hablar de volatile
y sus programas han funcionado durante años".
Entonces mi pregunta es:
¿Puede alguien proporcionar un ejemplo de programa / fragmento de Java que muestre de manera confiable los problemas de visibilidad de los datos?
Creo que ejecutar un programa y ver el NPE inesperado o el valor de la variable obsoleta ayudaría más, que solo explicaciones teóricas, que no se puede demostrar.
¡Muchas gracias por tu ayuda!
Actualización: solo para enfatizar el punto nuevamente. He leído Java Concurreny en Practice y conozco ejemplos que teóricamente tienen problemas de visibilidad. Lo que estoy buscando es una forma de demostrarlos . No estoy seguro de que esto sea realmente posible, pero tal vez haya una configuración jvm o algo similar que lo permita.
¿Puede alguien proporcionar un ejemplo de programa / fragmento de Java que muestre de manera confiable los problemas de visibilidad de los datos?
No, no hay ningún ejemplo fiable que muestre problemas de visibilidad de datos.
La razón es que cualquier ejecución válida de un programa con volatile
también es una ejecución válida del mismo programa sin volatile
. (¡Sin embargo, lo opuesto obviamente no es cierto!)
Al modificar el ejemplo here al eliminar operaciones, he encontrado un ejemplo que falla sistemáticamente en mi entorno (el hilo nunca deja de ejecutarse).
// Java environment:
// java version "1.6.0_0"
// OpenJDK Runtime Environment (IcedTea6 1.6.1) (6b16-1.6.1-3ubuntu3)
// OpenJDK 64-Bit Server VM (build 14.0-b16, mixed mode)
public class Test2 extends Thread {
boolean keepRunning = true;
public static void main(String[] args) throws InterruptedException {
Test2 t = new Test2();
t.start();
Thread.sleep(1000);
t.keepRunning = false;
System.out.println(System.currentTimeMillis() + ": keepRunning is false");
}
public void run() {
while (keepRunning)
{}
}
}
Tenga en cuenta que este tipo de problemas dependen bastante del compilador / tiempo de ejecución / sistema. En particular, el compilador puede determinar agregar instrucciones para leer la variable de la memoria, incluso si no es volátil, por lo que el código funcionaría, la VM y JIT pueden optimizar las lecturas de la memoria y usar solo registros, e incluso el el procesador puede reordenar las instrucciones, lo que no afectaría a este caso, pero en otros casos multiproceso, puede afectar el estado percibido de otros hilos si se modifican más de una variable.
El ejemplo más común while(keepRunning)
la importancia de usar volátiles es el ejemplo while(keepRunning)
:
public class Test extends Thread {
boolean keepRunning = true;
public static void main(String[] args) throws InterruptedException {
Test t = new Test();
t.start();
Thread.sleep(1000);
t.keepRunning = false;
System.out.println(System.currentTimeMillis() + ": keepRunning is false");
}
public void run() {
while (keepRunning)
System.out.println(System.currentTimeMillis() + ": " + keepRunning);
}
}
Dado que keepRunning
puede (válidamente) mantenerse en la memoria caché del hilo que ejecuta el ciclo while, este programa puede imprimir "verdadero" para keepRunning
en keepRunning
mucho después de que keepRunning
se establezca en falso.
Sin embargo , tenga en cuenta que no existe una forma confiable de exponer las condiciones de carrera. (Vea mi otra respuesta.) Este ejemplo puede exponerlo bajo ciertas circunstancias en ciertas combinaciones de hardware / sistema operativo / jvm.
Haga que lean el libro Java Concurrency in Practice del gurú de la concurrencia de Java Brian Goetz. ¡Ese libro es una lectura obligada para cualquiera que tenga que escribir cualquier software concurrente serio en Java!
Por supuesto, decir "nunca he oído hablar de volatile
y mis programas han funcionado durante años" es un argumento tonto de la ignorancia .
Tengo un fragmento de código para ti:
package test;
public class LoopTest {
private boolean done = false;
/**
* @param args
*/
public void start() {
System.out.println(System.getProperty("java.vm.name"));
System.out.println(System.getProperty("java.version"));
for (int i = 0; i < 100; i++) {
startNewThread();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
done = true;
System.out.println("forcing end");
}
private void startNewThread() {
new Thread(new Runnable() {
public void run() {
long i = 0;
while(!done) {
i++;
if(i % 100L == 0) {
System.out.println("still working " + i);
}
}
System.out.println("ending " + i);
}
}).start();
}
public static void main(String[] args) {
new LoopTest().start();
}
}
Este ejemplo ejecutado en el modo Servidor JVM generó esta salida en mi máquina:
..
..
ending 14100
still working 14800
ending 14800
still working 26500
ending 26500
still working 18200
ending 18200
still working 9400
ending 9400
still working 1300
ending 1300
still working 59500
ending 59500
still working 1700
still working 75400
ending 75400
still working 33500
ending 33500
still working 36100
ending 36100
still working 121000
ending 121000
still working 3000
ending 3000
ending 1700
still working 5900
ending 5900
still working 7800
ending 7800
still working 7800
ending 7800
still working 6800
ending 6800
still working 5300
ending 5300
still working 9100
still working 10600
ending 10600
still working 9600
ending 9600
still working 10000
ending 10000
ending 9100
still working 1700
ending 1700
..
..
Mire las declaraciones de "finalización #": todas ellas tienen un número que es un múltiplo de 100, lo que es poco probable que ocurra, creo. Mi interpretación es que hay un problema de visibilidad que causa que los hilos se sigan leyendo done == false aunque ya se haya actualizado a verdadero. Después de la llamada al método Synchronized System.out.println () con la instrucción "still working #", los hilos leen el valor actualizado de "done" y "exit".
¿O alguien ve un error en mi código / interpretación?
Una extensión del código de @David (configuración Java6 64bit, Eclipse Juno SR2):
public class NoVisibility_Demonstration extends Thread {
boolean keepRunning = true;
public static void main(String[] args) throws InterruptedException {
NoVisibility_Demonstration t = new NoVisibility_Demonstration();
t.start();
Thread.sleep(1000);
t.keepRunning = false;
System.out.println(System.currentTimeMillis() + ": keepRunning is false");
}
public void run() {
int x = 10;
while (keepRunning)
{
//System.out.println("If you uncomment this line, the code will work without the visibility issue");
x++;
}
System.out.println("x:"+x);
}
}
Usando este ejemplo, puede mostrar ambos escenarios. Cuando descomenta la línea en el ciclo while en run (), se resuelve el problema de visibilidad. La razón es que las instrucciones println () usan sincronización. Discusión detallada HERE
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;
}
}
Coloque la llamada Thread.sleep()
durante 20 segundos, lo que sucederá es que JIT se activará durante esos 20 segundos y optimizará la comprobación y el valor en caché o eliminará la condición por completo. Y entonces el código fallará en la visibilidad.
Para evitar que esto suceda, DEBE usar volatile
.