Hay dos usos principales de AtomicInteger
:
Como un contador atómico ( incrementAndGet()
, etc.) que puede ser usado por muchos hilos simultáneamente
Como una primitiva que admite la instrucción de comparar e intercambiar ( compareAndSet()
) para implementar algoritmos sin bloqueo.
Aquí hay un ejemplo de generador de números aleatorios sin bloqueo de Java Concurrency In Practice 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 manera que incrementAndGet()
, pero realiza un cálculo arbitrario ( calculateNext()
) en lugar de un incremento (y procesa el resultado antes del retorno).
read
ywrite that value + 1
, esto se detecta en lugar de sobrescribir la actualización anterior (evitando el problema de "actualización perdida"). Este es realmente un caso especial decompareAndSet
- si el valor anterior era2
, la clase realmente llamacompareAndSet(2, 3)
- entonces si otro hilo ha modificado el valor mientras tanto, el método de incremento se reinicia efectivamente desde el principio.El ejemplo más simple que se me ocurre es incrementar la operación atómica.
Con entradas estándar:
Con AtomicInteger:
Esta última es una forma muy simple 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 más compleja sin sincronización mediante el uso
compareAndSet()
como un tipo de bloqueo optimista: obtenga el valor actual, calcule el resultado en función de esto, establezca este resultado si el valor sigue siendo la entrada utilizada para hacer el cálculo, o comience de nuevo, pero el los ejemplos de conteo son muy útiles, y a menudo los utilizoAtomicIntegers
para contar y generadores únicos para toda la VM si hay alguna pista de que hay varios subprocesos involucrados, porque son tan fáciles de trabajar que casi consideraría una optimización prematura el uso simpleints
.Si bien casi siempre puede lograr las mismas garantías de sincronización
ints
y lassynchronized
declaraciones apropiadas , lo mejor de estoAtomicInteger
es que la seguridad de los hilos está integrada en el objeto real, en lugar de tener que preocuparse por las posibles entrelazamientos y monitores que se mantienen, de cada método eso sucede para acceder alint
valor. Es mucho más difícil violar accidentalmente la seguridad de subprocesos al llamargetAndIncrement()
que al regresari++
y recordar (o no) adquirir de antemano el conjunto correcto de monitores.fuente
Si observa los métodos que AtomicInteger tiene, notará que tienden a corresponder a operaciones comunes en ints. Por ejemplo:
es la versión segura de este subproceso:
El mapa de métodos
++i
es el siguiente : isi.incrementAndGet()
i++
isi.getAndIncrement()
--i
isi.decrementAndGet()
i--
isi.getAndDecrement()
i = x
isi.set(x)
x = i
is isx = i.get()
También hay otros métodos de conveniencia, como
compareAndSet
oaddAndGet
fuente
El uso principal de
AtomicInteger
es cuando se encuentra en un contexto multiproceso y necesita realizar operaciones seguras de subprocesos en un entero sin usarsynchronized
. La asignación y recuperación en el tipo primitivoint
ya son atómicas, peroAtomicInteger
vienen con muchas operaciones que no son atómicasint
.Los más simples son los
getAndXXX
oxXXAndGet
. Por ejemplo,getAndIncrement()
es un equivalente atómico ali++
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, cerraduras, pestillos, etc.Usar el
AtomicInteger
es más rápido y más legible que realizarlo usando la sincronización.Una prueba simple:
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 para sincronizar (
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 atomicidad, AtomicInteger se puede usar como una versión mutable de,
Integer
por ejemplo, enMap
s como valores.fuente
AtomicInteger
como clave de mapa, porque usa laequals()
implementación predeterminada , que seguramente no es lo que esperarías de la semántica si se usara en un mapa.Por ejemplo, tengo una biblioteca que genera instancias de alguna clase. Cada una de estas instancias debe tener una ID entera única, ya que estas instancias representan comandos que se envían a un servidor, y cada comando debe tener una ID única. Dado que varios subprocesos pueden enviar comandos simultáneamente, utilizo un AtomicInteger para generar esas ID. Un enfoque alternativo sería usar algún tipo de bloqueo y un número entero regular, pero eso es más lento y menos elegante.
fuente
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 arquitectura, por lo que es más fácil y probablemente más optimizado que cualquier MutableInteger que pueda codificar rápidamente. Dicho eso, se siente como un abuso de la clase.
fuente
En Java 8, las clases atómicas se han ampliado con dos funciones interesantes:
Ambos están utilizando la función updateFunction para realizar la actualización del valor atómico. La diferencia es que el primero devuelve el valor anterior y el segundo devuelve el valor nuevo. La función updateFunction se puede implementar para realizar operaciones de "comparación y configuración" más complejas que la estándar. Por ejemplo, puede verificar que el contador atómico no descienda por debajo de cero, normalmente requeriría sincronización, y aquí el código no tiene bloqueo:
El código está tomado de Java Atomic Example .
fuente
Usualmente uso AtomicInteger cuando necesito dar ID a objetos a los que se puede acceder o crear desde múltiples hilos, y generalmente lo uso como un atributo estático en la clase a la que accedo en el constructor de los objetos.
fuente
Puede implementar bloqueos sin bloqueo utilizando compareAndSwap (CAS) en enteros atómicos o longs. El documento "Tl2" Software Transactional Memory describe esto:
Lo que está describiendo es leer primero el entero atómico. Divida esto en un bit de bloqueo ignorado y el número de versión. Intente escribir CAS como el bit de bloqueo borrado con el número de versión actual en el conjunto de bits de bloqueo y el siguiente número de versión. Haz un bucle hasta que tengas éxito y seas el hilo propietario de la cerradura. 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 hace que sea muy eficiente. También afirma:
fuente
La clave es que permiten el acceso concurrente y la modificación de forma segura. Se usan comúnmente como contadores en un entorno multiproceso; antes de su introducción, tenía que ser una clase escrita por el usuario que envolviera los diversos métodos en bloques sincronizados.
fuente
Usé AtomicInteger para resolver el problema del Dining Philosopher.
En mi solución, se utilizaron instancias AtomicInteger para representar los tenedores, se necesitan dos por filósofo. Cada filósofo se identifica como un número entero, del 1 al 5. Cuando un filósofo usa una bifurcación, el AtomicInteger tiene el valor del filósofo, del 1 al 5; de lo contrario, la bifurcación no se usa, por lo que el valor del AtomicInteger es -1 .
El AtomicInteger permite verificar si una bifurcación está libre, valor == - 1, y establecerla en el propietario de la bifurcación si está libre, en una operación atómica. Ver el código a continuación.
Debido a que el método compareAndSet no bloquea, debería aumentar el rendimiento, más trabajo realizado. Como ya sabrá, el problema de Dining Philosophers se usa cuando se necesita acceso controlado a los recursos, es decir, se necesitan tenedores, como un proceso necesita recursos para continuar trabajando.
fuente
Ejemplo simple para la función compareAndSet ():
El impreso es: valor anterior: 0 El valor se actualizó y es 6 Otro ejemplo simple:
El impreso es: Valor anterior: 0 El valor no se actualizó
fuente