initialize how atomicinteger java multithreading thread-safety atomic volatile

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.



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:

  1. count = 9 actualmente.
  2. El hilo A ejecuta if(count.get() <10) y se vuelve true y se detiene allí.
  3. El subproceso B se ejecuta if(count.get() <10) y se vuelve true también, así que ejecuta count.incrementAndGet() y finaliza. Ahora count = 10 .
  4. El subproceso A se reanuda y ejecuta count.incrementAndGet() , ahora count = 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 }