java - Volatile boolean vs atomic boolean
concurrency atomicboolean (10)
¿Qué hace AtomicBoolean que un booleano volátil no puede lograr?
Volatile boolean vs atomic boolean
Las clases Atómicas * envuelven una primitiva volátil del mismo tipo. De la fuente:
public class AtomicLong extends Number implements java.io.Serializable {
...
private volatile long value;
...
public final long get() {
return value;
}
...
public final void set(long newValue) {
value = newValue;
}
Por lo tanto, si todo lo que está haciendo es obtener y configurar un Atomic *, entonces también podría tener un campo volátil.
¿Qué hace AtomicBoolean que un booleano volátil no puede lograr?
Sin embargo, lo que las clases Atomic * le brindan son métodos que proporcionan una funcionalidad más avanzada como incrementAndGet()
, compareAndSet()
y otros que implementan múltiples operaciones (obtener / incrementar / configurar, probar / configurar) sin bloqueo. Por eso las clases atómicas * son tan poderosas.
Por ejemplo, si varios subprocesos utilizan el siguiente código usando ++
, habrá condiciones de carrera porque ++
es en realidad: obtener, incrementar y establecer.
private volatile value;
...
// race conditions here
value++;
Sin embargo, el siguiente código funcionará en un entorno de subprocesos múltiples de forma segura:
private final AtomicLong value = new AtomicLong();
...
value.incrementAndGet();
También es importante tener en cuenta que envolver su campo volátil con la clase Atomic * es una buena manera de encapsular el recurso compartido crítico desde el punto de vista de un objeto. Esto significa que los desarrolladores no pueden lidiar con el campo asumiendo que no se comparte, posiblemente inyectando problemas con un campo ++; u otro código que introduzca condiciones de carrera.
El tipo primitivo booleano es atómico para las operaciones de escritura y lectura, y el principio volátil garantiza el principio de suceso. Entonces, si necesita un simple get () y set () entonces no necesita el AtomicBoolean.
Por otro lado, si necesita implementar alguna verificación antes de establecer el valor de una variable, por ejemplo, "si es verdadero, entonces establezca en falso", entonces también debe realizar esta operación de forma atómica, en este caso use compareAndSet y otros métodos proporcionados por AtomicBoolean, ya que si intenta implementar esta lógica con un booleano volátil, necesitará cierta sincronización para asegurarse de que el valor no haya cambiado entre obtener y establecer.
No puede hacer compareAndSet
, getAndSet
como operación atómica con booleano volátil (a menos que, por supuesto, lo sincronice).
Recuerda el IDIOM -
LEA - MODIFICAR - ESCRIBA esto que no puede lograr con volatile
Si hay varios subprocesos que acceden a la variable de nivel de clase, cada subproceso puede mantener una copia de esa variable en su caché de ubicación de subproceso.
Si la variable es volátil, evitará que los subprocesos conserven la copia de la variable en el caché threadlocal.
Las variables atómicas son diferentes y permiten la modificación atómica de sus valores.
Utilizo campos volátiles cuando dicho campo SOLO ACTUALIZADO por su subproceso propietario y el valor solo lo leen otros subprocesos, puede considerarlo como un escenario de publicación / suscripción donde hay muchos observadores pero solo un editor. Sin embargo, si esos observadores deben realizar alguna lógica basada en el valor del campo y luego rechazar un nuevo valor, entonces voy con Atomic * vars o bloqueos o bloques sincronizados, lo que más me convenga. En muchos escenarios concurrentes, se reduce a obtener el valor, se compara con otro y se actualiza si es necesario, por lo tanto, los métodos compareAndSet y getAndSet presentes en las clases Atomic *.
Revise los JavaDocs del paquete java.util.concurrent.atomic para obtener una lista de las clases atómicas y una excelente explicación de cómo funcionan (recién aprendí que están libres de bloqueo, por lo que tienen una ventaja sobre los bloqueos o bloques sincronizados)
AtomicBoolean
tiene métodos que realizan sus operaciones compuestas de forma atómica y sin tener que usar un bloque synchronized
. Por otro lado, el volatile boolean
solo puede realizar operaciones compuestas si se hace dentro de un bloque synchronized
.
Los efectos de memoria de lectura / escritura en volatile boolean
son idénticos a los métodos get
y set
de AtomicBoolean
respectivamente.
Por ejemplo, el método compareAndSet
realizará atómicamente lo siguiente (sin un bloque synchronized
):
if (value == expectedValue) {
value = newValue;
return true;
} else {
return false;
}
Por lo tanto, el método compareAndSet
le permitirá escribir código que se garantiza que se ejecutará solo una vez, incluso cuando se le llame desde varios subprocesos. Por ejemplo:
final AtomicBoolean isJobDone = new AtomicBoolean(false);
...
if (isJobDone.compareAndSet(false, true)) {
listener.notifyJobDone();
}
Se garantiza que solo notificará al oyente una vez (suponiendo que ningún otro subproceso AtomicBoolean
nuevo a false
una vez que se haya establecido en true
).
volatile
palabra clave volatile
garantiza una relación antes de que suceda entre hilos que comparten esa variable. No garantiza que 2 o más subprocesos no se interrumpan entre sí al acceder a esa variable booleana.
Si solo tiene un hilo que modifica su booleano, puede usar un booleano volátil (por lo general, lo hace para definir una variable de stop
marcada en el bucle principal del hilo).
Sin embargo, si tiene varios subprocesos que modifican el booleano, debe usar un AtomicBoolean
. De lo contrario, el siguiente código no es seguro:
boolean r = !myVolatileBoolean;
Esta operación se realiza en dos pasos:
- Se lee el valor booleano.
- Se escribe el valor booleano.
Si otro hilo modifica el valor entre #1
y 2#
, es posible que obtenga un resultado incorrecto. AtomicBoolean
métodos AtomicBoolean
evitan este problema haciendo los pasos #1
y #2
atómicamente.
Ellos son totalmente diferentes. Considere este ejemplo de un entero volatile
:
volatile int i = 0;
void incIBy5() {
i += 5;
}
Si dos subprocesos llaman a la función al mismo tiempo, podría ser 5 después, ya que el código compilado será similar a este (excepto que no se puede sincronizar en int
):
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
Si una variable es volátil, cada acceso atómico a ella está sincronizado, pero no siempre es obvio lo que realmente califica como acceso atómico. Con un objeto Atomic*
, se garantiza que cada método es "atómico".
Por lo tanto, si usa un AtomicInteger
y getAndAdd(int delta)
, puede estar seguro de que el resultado será 10
. De la misma manera, si dos subprocesos niegan una variable boolean
simultáneamente, con un AtomicBoolean
puede estar seguro de que tiene el valor original después, con un volatile boolean
, no puede.
Entonces, cuando tenga más de un hilo modificando un campo, necesita hacerlo atómico o usar sincronización explícita.
El propósito de la volatile
es diferente. Considera este ejemplo
volatile boolean stop = false;
void loop() {
while (!stop) { ... }
}
void stop() { stop = true; }
Si tiene un subproceso que ejecuta loop()
y otro subproceso que llama a stop()
, es posible que se encuentre con un bucle infinito si omite volatile
, ya que el primer subproceso podría almacenar en caché el valor de la detención. Aquí, la volatile
sirve como una sugerencia para que el compilador sea un poco más cuidadoso con las optimizaciones.