¿Cómo demostrar los problemas de visibilidad de java multithreading?


Si se accede a variables en Java desde múltiples subprocesos, se debe asegurarse de que se publiquen de forma segura. Esto generalmente significa usar synchronized o volatile.

Tengo la impresión de que algunos de mis colegas no toman este tema en serio, ya que "nunca habían oído hablar de volatile antes y sus programas han funcionado durante años".

Así que mi pregunta es:

¿Puede alguien proporcionar un ejemplo de programa/fragmento de código Java, que muestra de forma fiable la visibilidad de los datos problema.

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 pueden demostrar.

Muchas Gracias por su ayuda!

Actualización: Solo para enfatizar el punto de nuevo. He leído Java Concurreny en la práctica y conozco ejemplos que teóricamente tienen problemas de visibilidad. Lo que estoy buscando es una manera de demostrarlos. No lo soy. claro, que esto es realmente posible, pero tal vez hay una configuración jvm o algo similar que lo permite.

Author: Joe23, 2010-05-07

7 answers

Al modificar el ejemplo aquí al eliminar operaciones, se me ocurrió un ejemplo que falla consistentemente en mi entorno (el subproceso 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/runtime/system. En particular, el compilador puede determinar agregar instrucciones para leer la variable desde la memoria incluso si no es volátil so por lo que el código funcionaría work, la vm y jit pueden optimizar las lecturas de la memoria y usar solo registros, e incluso el procesador puede reordenar instrucciones that que no afectarían a este caso, pero en otros casos multiproceso puede afectar el estado percibido de otros subprocesos si se modifican más de una variable.

 18
Author: David Rodríguez - dribeas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:16:48

¿Puede alguien proporcionar un ejemplo de programa/fragmento de código Java, que muestra de forma fiable los problemas de visibilidad de los datos.

No, no hay un ejemplo fiable que muestre problemas de visibilidad de datos.

La razón es que cualquier ejecución válida de un programa con volatile es también una ejecución válida del mismo programa sin volatile. (¡Lo contrario obviamente no es cierto!)

 6
Author: aioobe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-07 10:05:21

El ejemplo más común que puso de relieve la importancia de usar volátil 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 caché del hilo que ejecuta el bucle while, este programa puede imprimir "true" para keepRunning mucho después de que keepRunning se establezca en false.

Note sin embargo, que no hay ninguna manera confiable de exponer las condiciones de raza. (Ver mi otra respuesta.) Este ejemplo puede exponerlo bajo ciertas circunstancias en ciertas combinaciones de hardware / sistema operativo / jvm.

 5
Author: aioobe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-07 09:20:16

Haz 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 ignorancia.

 1
Author: Jesper,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-07 08:13:18

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 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
..
..

Mira las declaraciones "ending#": todas ellas tienen un número que es un múltiplo de 100, lo cual es muy poco probable que suceda, creo. Mi interpretación es que hay un problema de visibilidad que hace que los hilos todavía se lean done == false aunque ya se ha actualizado a true. Después de la llamada al sistema sincronizado.fuera.método 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?

 1
Author: soil7c3,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-25 08:56:11
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 almacenará en caché el valor o eliminará la condición por completo. Y así el código fallará en visibilidad.

Para evitar que eso suceda DEBES usar volatile.

 0
Author: Narendra Pathai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-27 21:32:22

Una extensión al 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 bucle while en run(), se resuelve el problema de visibilidad. La razón es que las instrucciones println () usan sincronización. Debate detalladoAQUÍ

 0
Author: Biman Tripathy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:45:22