Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AtomicInteger: keep non-negative

Is there a way to perform a "decrement if result is positive or zero" operation with an AtomicInteger? To clarify the desired behavior:

  • if the current value is greater than zero, decrement
  • if the current value is equal to zero, do nothing
  • (negative current value is not handled)
like image 705
pmf Avatar asked Oct 24 '14 06:10

pmf


2 Answers

In Java 8, yes:

atomicInteger.updateAndGet(i -> i > 0 ? i - 1 : i); 

Before Java 8, no.

like image 88
JB Nizet Avatar answered Oct 28 '22 18:10

JB Nizet


I suppose you could do something like this pre-Java 8:

int val = atomicInt.get();
boolean success = false;
while(val > 0 && !success) {
    success = atomicInt.compareAndSet(val, val - 1);
    if(!success) {
        // Try again if the value is still > 0
        val = atomicInt.get();
    }
}
// Check 'success' to see if it worked

Not the most elegant code, but I think it does the trick.


Informal proof of correctness (by @Stephen C)

In the case where there is no other thread modifying the AtomicInteger, success will be set to true on the first compareAndSet call. So the code will be equivalent to

int val = atomicInt.get();
if (val > 0) {
    atomicInt.compareAndSet(val, val - 1);
}

which is clearly correct.

In the case where some other thread modifies the AtomicInteger, between the get and the compareAndSet then the latter call will fail because the current value is no longer equal to val. So what happens then is that we call atomicInt.get() again to get the updated value ... and repeat. We keep repeating until either we succeeded in the compareAndSet OR the current val is less zero or less.

The net effect is that this thread EITHER decrements the AtomicInteger once, OR it gives up because it sees that the value is zero.

Note the following caveats:

  1. The retry loop may result in another thread "overtaking" and getting its decrement in before our thread. (Another way of saying that is to say the algorithm is not "fair".)
  2. If you immediately observed the value of the AtomicInteger after this sequence, you may observe that its value has changed ... again.
  3. It is theoretically possible for the code to loop indefinitely. But that requires other threads to be continually updating the AtomicInteger.

However, none of these caveats is a violation of the (assumed) requirements.

like image 43
BarrySW19 Avatar answered Oct 28 '22 18:10

BarrySW19