example atomicinteger java concurrency atomic

java - example - Usos prácticos de AtomicInteger.



atomicinteger java (10)

Entiendo que AtomicInteger y otras variables atómicas permiten accesos concurrentes. ¿En qué casos se utiliza esta clase normalmente?


Como dijo gabuzo, a veces uso AtomicIntegers cuando quiero pasar un int por referencia. Es una clase incorporada que tiene un código específico de la arquitectura, por lo que es más fácil y probablemente más optimizado que cualquier MutableInteger que pudiera codificar rápidamente. Dicho esto, se siente como un abuso de la clase.


El ejemplo más simple que se me ocurre es hacer un incremento de una operación atómica.

Con ints estándar:

private volatile int counter; public int getNextUniqueIndex() { return counter++; // Not atomic, multiple threads could get the same result }

Con AtomicInteger:

private AtomicInteger counter; public int getNextUniqueIndex() { return counter.getAndIncrement(); }

Esta última es una forma muy sencilla de realizar efectos de mutaciones simples (especialmente el conteo o la indexación única), sin tener que recurrir a la sincronización de todos los accesos.

Se puede emplear una lógica sin sincronización más compleja utilizando compareAndSet() como un tipo de bloqueo optimista: obtenga el valor actual, calcule el resultado en base a esto, establezca este resultado si el valor sigue siendo la entrada utilizada para hacer el cálculo, o comience de nuevo - pero los ejemplos de conteo son muy útiles, y a menudo AtomicIntegers para el conteo y generadores únicos en toda la máquina virtual si hay algún indicio de múltiples hilos involucrados, porque son tan fáciles de trabajar que casi lo considero prematuro Optimización para utilizar ints simples.

Si bien casi siempre se pueden lograr las mismas garantías de sincronización con los ints y las declaraciones synchronized adecuadas, la belleza de AtomicInteger es que la seguridad de subprocesos está incorporada en el objeto real en sí, en lugar de que usted deba preocuparse por los posibles intrusiones y monitores mantenidos. De cada método que pasa para acceder al valor int . Es mucho más difícil violar accidentalmente la seguridad de subprocesos cuando se llama a getAndIncrement() que cuando se devuelve i++ y se recuerda (o no) para adquirir el conjunto correcto de monitores de antemano.


El uso principal de AtomicInteger es cuando estás en un contexto multiproceso y necesitas realizar operaciones seguras de subprocesos en un entero sin usar synchronized . La asignación y recuperación en el tipo primitivo int ya son atómicas, pero AtomicInteger viene con muchas operaciones que no son atómicas en int .

Los más simples son los getAndXXX o xXXAndGet . Por ejemplo, getAndIncrement() es un equivalente atómico a i++ que no es atómico porque en realidad es un atajo para tres operaciones: recuperación, adición y asignación. compareAndSet es muy útil para implementar semáforos, bloqueos, cierres, etc.

El uso de AtomicInteger es más rápido y más legible que hacer lo mismo usando la sincronización.

Una prueba simple:

public synchronized int incrementNotAtomic() { return notAtomic++; } public void performTestNotAtomic() { final long start = System.currentTimeMillis(); for (int i = 0 ; i < NUM ; i++) { incrementNotAtomic(); } System.out.println("Not atomic: "+(System.currentTimeMillis() - start)); } public void performTestAtomic() { final long start = System.currentTimeMillis(); for (int i = 0 ; i < NUM ; i++) { atomic.getAndIncrement(); } System.out.println("Atomic: "+(System.currentTimeMillis() - start)); }

En mi PC con Java 1.6, la prueba atómica se ejecuta en 3 segundos, mientras que la sincronizada se ejecuta en aproximadamente 5,5 segundos. El problema aquí es que la operación de sincronización ( notAtomic++ ) es realmente corta. Por lo tanto, el costo de la sincronización es realmente importante en comparación con la operación.

Además de la atomicidad, AtomicInteger puede usarse como una versión mutable de Integer por ejemplo, en Map s como valores.


En Java 8 las clases atómicas se han ampliado con dos funciones interesantes:

  • int getAndUpdate (IntUnaryOperator updateFunction)
  • int updateAndGet (IntUnaryOperator updateFunction)

Ambos están utilizando updateFunction para realizar la actualización del valor atómico. La diferencia es que el primero devuelve el valor antiguo y el segundo devuelve el nuevo valor. El updateFunction puede implementarse para realizar operaciones más complejas de "comparar y configurar" que la estándar. Por ejemplo, puede verificar que el contador atómico no esté por debajo de cero, normalmente requeriría sincronización, y aquí el código está libre de bloqueo:

public class Counter { private final AtomicInteger number; public Counter(int number) { this.number = new AtomicInteger(number); } /** @return true if still can decrease */ public boolean dec() { // updateAndGet(fn) executed atomically: return number.updateAndGet(n -> (n > 0) ? n - 1 : n) > 0; } }

El código está tomado de Java Atomic Example .


La clave es que permiten el acceso concurrente y la modificación de forma segura. Se usan comúnmente como contadores en un entorno de multiproceso: antes de su introducción, esto tenía que ser una clase escrita por el usuario que envolvía los diversos métodos en bloques sincronizados.


Normalmente uso AtomicInteger cuando necesito dar identificadores a objetos a los que se puede acceder o crear desde múltiples subprocesos, y generalmente lo uso como un atributo estático en la clase a la que accedo en el constructor de los objetos.


Por ejemplo, tengo una biblioteca que genera instancias de alguna clase. Cada una de estas instancias debe tener un ID entero único, ya que estas instancias representan comandos que se envían a un servidor, y cada comando debe tener una ID única. Dado que se permite que varios subprocesos envíen comandos simultáneamente, utilizo un AtomicInteger para generar esos ID. Un enfoque alternativo sería utilizar algún tipo de bloqueo y un entero normal, pero eso es más lento y menos elegante.


Puede implementar bloqueos no bloqueantes utilizando compareAndSwap (CAS) en enteros atómicos o largos. El papel de la memoria transaccional del software "Tl2" describe esto:

Asociamos un bloqueo de escritura con una versión especial a cada ubicación de memoria negociada. En su forma más simple, el bloqueo de escritura versionado es un spinlock de una sola palabra que utiliza una operación CAS para adquirir el bloqueo y una tienda para liberarlo. Como solo se necesita un bit para indicar que se ha realizado el bloqueo, utilizamos el resto de la palabra de bloqueo para mantener un número de versión.

Lo que está describiendo es primero leer el entero atómico. Divida esto en un bit de bloqueo ignorado y el número de versión. Intente escribirlo como el bit de bloqueo borrado con el número de versión actual al conjunto de bits de bloqueo y el siguiente número de versión. Bucle hasta que tenga éxito y usted es el hilo que posee el bloqueo. Desbloquee configurando el número de versión actual con el bit de bloqueo desactivado. El documento describe el uso de los números de versión en los bloqueos para coordinar que los hilos tengan un conjunto consistente de lecturas cuando escriben.

Este artículo describe que los procesadores tienen soporte de hardware para operaciones de comparación e intercambio, lo que lo hace muy eficiente. También afirma:

los contadores basados ​​en CAS sin bloqueo que utilizan variables atómicas tienen un mejor rendimiento que los contadores basados ​​en bloqueo en contienda de baja a moderada


Si observa los métodos que tiene AtomicInteger, notará que tienden a corresponder a operaciones comunes en ints. Por ejemplo:

static AtomicInteger i; // Later, in a thread int current = i.incrementAndGet();

Es la versión segura de subprocesos de esto:

static int i; // Later, in a thread int current = ++i;

Los métodos se mapean así:
++i is i.incrementAndGet()
i++ es i.getAndIncrement()
--i es i.decrementAndGet()
i-- es i.getAndDecrement()
i = x es i.set(x)
x = i es x = i.get()

También hay otros métodos de conveniencia, como compareAndSet o addAndGet


Hay dos usos principales de AtomicInteger :

  • Como un contador atómico ( incrementAndGet() , etc.) que pueden ser utilizados por muchos hilos simultáneamente

  • Como una primitiva que admite la instrucción de compare-and-swap ( compareAndSet() ) para implementar algoritmos de no bloqueo.

    Este es un ejemplo de un generador de números aleatorios no bloqueante de la Concurrencia de Java en Práctica de Brian Göetz :

    public class AtomicPseudoRandom extends PseudoRandom { private AtomicInteger seed; AtomicPseudoRandom(int seed) { this.seed = new AtomicInteger(seed); } public int nextInt(int n) { while (true) { int s = seed.get(); int nextSeed = calculateNext(s); if (seed.compareAndSet(s, nextSeed)) { int remainder = s % n; return remainder > 0 ? remainder : remainder + n; } } } ... }

    Como puede ver, básicamente funciona casi de la misma forma que incrementAndGet() , pero realiza un cálculo arbitrario ( calculateNext() ) en lugar de incremento (y procesa el resultado antes de la devolución).