variable definicion java concurrency visibility volatile happens-before

java - definicion - variable volatile



¿Qué tan profunda garantiza la publicación volátil? (1)

En el ámbito del modelo de memoria de Java actual, volatile no es igual a final . En otras palabras, no puede reemplazar final con volatile , y cree que las garantías de construcción segura son las mismas. En particular, esto teóricamente puede suceder:

public class M { volatile int x; M(int v) { this.x = v; } int x() { return x; } } // thread 1 m = new M(42); // thread 2 M lm; while ((lm = m) == null); // wait for it print(lm.x()); // allowed to print "0"

Por lo tanto, escribir el campo volatile en el constructor no es tan seguro.

Intuición: hay una carrera en m en el ejemplo anterior. Esa raza no se elimina volviendo el campo Mx volatile , solo hacer que el m sea volatile ayudaría. En otras palabras, el modificador volatile en ese ejemplo está en el lugar incorrecto para ser útil. En una publicación segura, debe tener "escrituras -> escritura volátil -> lectura volátil que observa escritura volátil -> lecturas (ahora observa escrituras antes de la escritura volátil)", y en su lugar tiene "escritura volátil -> escritura -> lectura - > lectura volátil (que no observa la escritura volátil) ".

Trivia 1: esta propiedad significa que podemos optimizar los volatile mucho más agresivamente en los constructores. Esto corrobora la intuición de que la tienda volátil no observada (y, de hecho, no se observa hasta que el constructor no se escape) puede relajarse.

Trivia 2: Esto también significa que no puede inicializar de forma segura volatile variables volatile . Reemplace M con AtomicInteger en el ejemplo anterior, ¡y tendrá un comportamiento peculiar en la vida real! Llame al new AtomicInteger(42) en un hilo, publique la instancia de forma insegura y get() en otro hilo: ¿tiene garantizado que observe 42 ? JMM, como se dijo, dice "no". Las revisiones más nuevas de Java Memory Model se esfuerzan por garantizar una construcción segura para todas las inicializaciones, para capturar este caso. Y muchos puertos que no son x86 donde eso importa ya han fortalecido esto para estar seguros.

Trivia 3: Doug Lea : "Este problema final vs volatile ha llevado a algunas construcciones sinuosas en java.util.concurrent para permitir 0 como el valor base / predeterminado en los casos en que no sería naturalmente. Esta regla es una mierda y debe cambiarse. "

Dicho esto, el ejemplo puede hacerse más astuto:

public class C { int v; C(int v) { this.x = v; } int x() { return x; } } public class M { volatile C c; M(int v) { this.c = new C(v); } int x() { while (c == null); // wait! return c.x(); } } // thread 1 m = new M(42); // thread 2 M lm; while ((lm = m) == null); // wait for it print(lm.x()); // always prints "42"

Si hay una lectura transitiva a través del campo volatile después de que la lectura volátil haya observado el valor escrito por la escritura volátil en el constructor, las reglas habituales de publicación segura se activan.

Como es sabido, garantizamos que si tenemos alguna referencia de objeto y esta referencia tiene el campo final, veremos todos los campos alcanzables del campo final (al menos cuando el constructor finalizó)

Ejemplo 1:

class Foo{ private final Map map; Foo(){ map = new HashMap(); map.put(1,"object"); } public void bar(){ System.out.println(map.get(1)); } }

Como he entendido en este caso, tenemos la garantía de que el método bar() siempre genera el object porque:
1. Enumeré el código completo de la clase Foo y el mapa es final;
2. Si algún hilo verá referencia de Foo y esta referencia! = Null, entonces tenemos garantías de que el valor de referencia del map alcanzable será real .

también creo que

Ejemplo 2:

class Foo { private final Map map; private Map nonFinalMap; Foo() { nonFinalMap = new HashMap(); nonFinalMap.put(2, "ololo"); map = new HashMap(); map.put(1, "object"); } public void bar() { System.out.println(map.get(1)); } public void bar2() { System.out.println(nonFinalMap.get(2)); } }

Aquí tenemos las mismas garantías sobre el método bar() pero bar2 puede lanzar NullPointerException pesar de que la asignación nonFinalMap ocurre antes de la asignación del map .

Quiero saber qué tal sobre volátil:

Ejemplo 3:

class Foo{ private volatile Map map; Foo(){ map = new HashMap(); map.put(1,"object"); } public void bar(){ System.out.println(map.get(1)); } }

Como entiendo bar() método bar() no puede arrojar NullPoinerException pero puede imprimir null ; (No estoy seguro de este aspecto)

Ejemplo 4:

class Foo { private volatile Map map; private Map nonVolatileMap; Foo() { nonVolatileMap= new HashMap(); nonVolatileMap.put(2, "ololo"); map = new HashMap(); map.put(1, "object"); } public void bar() { System.out.println(map.get(1)); } public void bar2() { System.out.println(nonFinalMap.get(2)); } }

Creo que aquí tenemos las mismas garantías sobre el método bar() También bar2() no puede lanzar NullPointerException porque la asignación nonVolatileMap escrito una asignación de mapa volátil más alta, pero puede generar un resultado nulo

Agregado después del comentario de Elliott Frisch

Publicación a través del ejemplo de raza:

public class Main { private static Foo foo; public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { foo = new Foo(); } }).start(); new Thread(new Runnable() { @Override public void run() { while (foo == null) ; // empty loop foo.bar(); } }).start(); } }

Por favor, procese o corrija mis comentarios a los fragmentos de código.