java - how - AtomicInteger y volatile
atomicinteger java 8 (4)
Sé que la volatile
permite la visibilidad, AtomicInteger
permite la atomicidad. Entonces, si uso un AtomicInteger
volátil, ¿significa que no tengo que usar más mecanismos de sincronización?
P.ej.
class A {
private volatile AtomicInteger count;
void someMethod(){
// do something
if(count.get() < 10) {
count.incrementAndGet();
}
}
¿Es este threadsafe?
Creo que Atomic*
realidad da tanto atomicidad como volatilidad. Entonces, cuando llama (diga) AtomicInteger.get()
, tiene la garantía de obtener el último valor. Esto se documenta en la documentación del paquete java.util.concurrent.atomic
:
Los efectos de memoria para accesos y actualizaciones de atómicos generalmente siguen las reglas para volátiles, como se indica en la sección 17.4 de la Especificación del lenguaje Java ™.
- get tiene los efectos de memoria de leer una variable volátil.
- set tiene los efectos de memoria de escribir (asignar) una variable volátil.
- lazySet tiene los efectos de memoria de escribir (asignar) una variable volátil, excepto que permite reordenar con acciones de memoria subsiguientes (pero no anteriores) que no imponen restricciones de reordenamiento con escrituras ordinarias no volátiles. Entre otros contextos de uso,> - lazySet puede aplicarse cuando se anula, por el bien de la recolección de basura, una referencia a la que nunca se accederá de nuevo.
- weakCompareAndSet lee atómicamente y escribe condicionalmente una variable, pero no crea ningún orden de suceso antes, por lo que no ofrece garantías con respecto a lecturas y escrituras anteriores o posteriores de cualquier variable que no sea el objetivo de weakCompareAndSet.
- compareAndSet y todas las demás operaciones de lectura y actualización, como getAndIncrement, tienen los efectos de memoria tanto de la lectura como de la escritura de variables volátiles.
Ahora si tienes
volatile AtomicInteger count;
la parte volatile
significa que cada subproceso usará la última referencia de AtomicInteger
, y el hecho de que sea un AtomicInteger
significa que también verá el último valor para ese objeto.
No es común (IME) necesitar esto, porque normalmente no reasignaría el count
para referirse a un objeto diferente. En su lugar, tendrías:
private final AtomicInteger count = new AtomicInteger();
En ese momento, el hecho de que sea una variable final
significa que todos los subprocesos tratarán con el mismo objeto, y el hecho de que sea un objeto Atomic*
significa que verán el último valor dentro de ese objeto.
La respuesta está ahí en este código
Este es el código fuente de AtomicInteger. El valor es volátil. Entonces, AtomicInteger usa Volatile dentro.
Para mantener la semántica original y admitir varios subprocesos, podría hacer algo como:
public class A {
private AtomicInteger count = new AtomicInteger(0);
public void someMethod() {
int i = count.get();
while (i < 10 && !count.compareAndSet(i, i + 1)) {
i = count.get();
}
}
}
Esto evita que cualquier hilo que llegue a ver el conteo llegue a 10.
Yo diría que no, no es seguro para subprocesos, si se define como seguro para subprocesos como tener el mismo resultado en modo de subproceso único y en modo multihebra. En el modo de un solo subproceso, el recuento nunca será mayor que 10, pero en el modo de subprocesos múltiples puede hacerlo.
El problema es que get
e incrementAndGet
es atómico pero un if
no lo es. Tenga en cuenta que una operación no atómica puede pausarse en cualquier momento. Por ejemplo:
-
count = 9
actualmente. - El hilo A ejecuta
if(count.get() <10)
y se vuelvetrue
y se detiene allí. - El subproceso B se ejecuta
if(count.get() <10)
y se vuelvetrue
también, así que ejecutacount.incrementAndGet()
y finaliza. Ahoracount = 10
. - El subproceso A se reanuda y ejecuta
count.incrementAndGet()
, ahoracount = 11
lo que nunca sucederá en modo de subproceso único.
Si desea que sea seguro para subprocesos sin usar synchronized
que es más lento, intente esta implementación en su lugar:
class A{
final AtomicInteger count;
void someMethod(){
// do something
if(count.getAndIncrement() <10){
// safe now
} else count.getAndDecrement(); // rollback so this thread did nothing to count
}