10

Is the incrementAndGet method of the following AtomicBigInteger implementation an atomic operation? I'm particularly wondering about the for (; ; ) part. Does the JVM somehow guarantee that each cycle in a for loop is executed atomicly?

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;
            }
        }
    }
}

I got this code from here: Possible to safely increment BigInteger in a thread safe way, perhaps with AtomicReference, w/o locking? However this implementation was making its rounds and you can find it on many different places across the internet.

eztam
  • 3,443
  • 7
  • 36
  • 54

2 Answers2

10

No, it is not atomic, but if another thread has modified the AtomicReference then the compareAndSet call will fail and it will loop again, get the value, increment it, and try to set it again. Sooner or later (probably) it will succeed and update the BigInteger held by the AtomicReference to the next number.

David Conrad
  • 15,432
  • 2
  • 42
  • 54
10

The method incrementAndGet in your class will not be atomic. Here's why.

The Atomic* classes use volatile value references. The memory offset to these values are also held inside the instances using which they are able to fetch-increment-compare-set in loop till the current thread is able to do all operations in one go (i.e., without another thread performing an increment in between).

This is possible for these Atomic*, as I see, due to the access the intrinsic "trusted" classes have to the Unsafe implementations. The Unsafe implementations have methods to compare-and-set atomically using native functions.

In cases like what you have mentioned, we will have to resort to either using synchronized blocks, its equivalent Lock based implementations or simply use the methods in AtomicReference. Like this:

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 ) );
    } 
}

However, since we are dealing with BigInteger, this implementation too will have to be reviewed because the number of iterations that AtomicReference.updateAndGet(..) may have to perform may be significant, because BigInteger.add( BigInteger ) involves a lot of steps, unlike an addition of two ints.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Sree Kumar
  • 2,012
  • 12
  • 9
  • Both answers to my question are very helpful. I accept yours because you've provided an alternative implementation that looks even better. I've successfully tested your implementation for correctness by running a huge amount of requests in parallel. I noticed, that your implementation is even slightly more performant. – eztam Nov 07 '21 at 14:04
  • Nice. A nice addition would be a no-arg constructor that initialized it with BigInteger.ZERO. – David Conrad Nov 07 '21 at 20:15