reset AtomicInteger to zero if it is going to overflow in thread safe way

Viewed 2275

I have a AtomicInteger that is being incremented by multiple threads.

I want to reset to zero if that AtomicInteger overflows but in an atomic way. I want to make sure counter variable value is always positive so as soon as clientCounter overflow or going to overflow, I will reset that AtomicInteger to zero. I came up with below code but I am not sure is it thread safe because I am doing addition to check and reset. Is there any better way to do this?

  private static final AtomicInteger clientCounter = new AtomicInteger(0);

  // called by multiple threads
  public static int getCounter() {
    final int counter = clientCounter.incrementAndGet();

    // reset "clientCounter" to zero as soon as it overflow
    // basically I don't want "counter" value should be negative if it overflow
    // is below thread safe?
    if (counter + 1 < 0) {
      clientCounter.set(0);
    }

    if (counter % SIZE == 0) {
        // save counter in database
    }

    return counter;
  }

Update:

Below is my method and I am using newCounter value to save in database as well. And the line where I am saving in database, it is asking to make newCounter as final variable and I can't make newCounter as final variable here. How to fix this now?

  public static int getCounter() {
    int counter;
    int newCounter;
    do {
        counter = clientCounter.get();
        newCounter = counter < Integer.MAX_VALUE ? counter + 1 : 1;
    } while (!clientCounter.compareAndSet(counter, newCounter));

    if (newCounter % SIZE == 0) {
      Executors.newSingleThreadExecutor().execute(new Runnable() {
        @Override
        public void run() {
          // this is asking "newCounter" to make final
          DBClient.getInstance().save(newCounter);
        }
      });
    }

    return newCounter;
  }
2 Answers

If I understand correctly, I think you need compareAndSet for this.

int counter;
int newCounter;
do {
    counter = clientCounter.get();
    newCounter = counter < Integer.MAX_VALUE ? counter + 1 : 1;
} while (!clientCounter.compareAndSet(counter, newCounter));
return newCounter;

The OP is a genuine use-case, but surprisingly there is little info out there on this. The counter needs to wrap back to 0 when it reaches a given MAX_COUNT. You could do this with the below

synchronized(clientCounter) {
  clientCounter.compareAndSet(MAX_COUNT, 0);
  return clientCounter.getAndIncrement();
}

It is important to synchronize the two statements above into one block to make sure only one thread at a time increments the counter so that the counter never gets incremented beyond the MAX_COUNT.

We can also achieve the same with the built-in getAndAccumulate function

return clientCounter.getAndAccumulate(MAX_COUNT, (current, maxCount) -> current < MAX_COUNT ? ++current : 0)
Related