¿Es el método incrementAndGet de la siguiente implementación AtomicBigInteger una operación atómica? Me pregunto particularmente sobre la parte for (; ; ) . ¿La JVM garantiza de alguna manera que cada ciclo en un bucle for se ejecute atómicamente?
public final class AtomicBigInteger { private final AtomicReference<BigInteger> valueHolder = new AtomicReference<>(); public AtomicBigInteger(BigInteger bigInteger) { valueHolder.set(bigInteger); } public BigInteger incrementAndGet() { for (; ; ) { BigInteger current = valueHolder.get(); BigInteger next = current.add(BigInteger.ONE); if (valueHolder.compareAndSet(current, next)) { return next; } } } }Obtuve este código de aquí: ¿Es posible incrementar BigInteger de manera segura de una manera segura para subprocesos, tal vez con AtomicReference, sin bloqueo? Sin embargo, esta implementación estaba dando vueltas y puede encontrarla en muchos lugares diferentes en Internet.
No, no es atómico, pero si otro subproceso ha modificado AtomicReference, entonces la llamada compareAndSet fallará y volverá a repetirse, obtendrá el valor, lo incrementará e intentará configurarlo nuevamente. Tarde o temprano (probablemente) tendrá éxito y actualizará el BigInteger en poder de AtomicReference al siguiente número.
El método incrementAndGet en su clase no será atómico. Este es el por qué.
Las clases Atomic* utilizan referencias de valores volatile . La compensación de memoria a estos valores también se mantiene dentro de las instancias mediante las cuales pueden buscar-incrementar-comparar-establecer en bucle hasta que el subproceso actual pueda realizar todas las operaciones de una sola vez (es decir, sin que otro subproceso realice un incremento en Entre).
Esto es posible para estos Atomic* , como veo, debido al acceso que tienen las clases "confiables" intrínsecas a las implementaciones no Unsafe . Las implementaciones no Unsafe tienen métodos para comparar y configurar atómicamente usando funciones native .
En casos como el que has mencionado, tendremos que recurrir a usar bloques synchronized , sus implementaciones equivalentes basadas en Lock o simplemente usar los métodos en AtomicReference . Me gusta esto:
public class AtomicBigInteger{ private final AtomicReference<BigInteger> valueHolder = new AtomicReference<>(); public AtomicBigInteger(BigInteger bigInteger) { valueHolder.set(bigInteger); } public BigInteger incrementAndGet() { return valueHolder.updateAndGet( bigInt -> bigInt.add( BigInteger.ONE ) ); } } Sin embargo, dado que estamos tratando con BigInteger , esta implementación también deberá revisarse porque la cantidad de iteraciones que AtomicReference.updateAndGet(..) puede tener que realizar puede ser significativa, porque BigInteger.add( BigInteger ) implica muchos pasos , a diferencia de una suma de dos int s.