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.