Practical uses for AtomicInteger
Asked Answered
A

13

287

I sort of understand that AtomicInteger and other Atomic variables allow concurrent accesses. In what cases is this class typically used though?

Autoionization answered 27/1, 2011 at 16:4 Comment(0)
R
227

There are two main uses of AtomicInteger:

  • As an atomic counter (incrementAndGet(), etc) that can be used by many threads concurrently

  • As a primitive that supports compare-and-swap instruction (compareAndSet()) to implement non-blocking algorithms.

    Here is an example of non-blocking random number generator from Brian Göetz's Java Concurrency In Practice:

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

    As you can see, it basically works almost the same way as incrementAndGet(), but performs arbitrary calculation (calculateNext()) instead of increment (and processes the result before return).

Rattlebrained answered 27/1, 2011 at 16:9 Comment(3)
I think I understand the first use. This is to make sure the counter has been incremented before an attribute is accessed again. Correct? Could you give a short example for the second use?Autoionization
Your understanding of the first use is kind of true - it simply ensures that if another thread modifies the counter between the read and write that value + 1 operations, this is detected rather than overwriting the old update (avoiding the "lost update" problem). This is actually a special case of compareAndSet - if the old value was 2, the class actually calls compareAndSet(2, 3) - so if another thread has modified the value in the meantime, the increment method effectively restarts from the beginning.Pattani
"remainder > 0 ? remainder : remainder + n;" in this expression is there a reason to add remainder to n when it is 0?Putandtake
P
132

The absolute simplest example I can think of is to make incrementing an atomic operation.

With standard ints:

private volatile int counter;

public int getNextUniqueIndex() {
    return counter++; // Not atomic, multiple threads could get the same result
}

With AtomicInteger:

private AtomicInteger counter;

public int getNextUniqueIndex() {
    return counter.getAndIncrement();
}

The latter is a very simple way to perform simple mutations effects (especially counting, or unique-indexing), without having to resort to synchronizing all access.

More complex synchronization-free logic can be employed by using compareAndSet() as a type of optimistic locking - get the current value, compute result based on this, set this result iff value is still the input used to do the calculation, else start again - but the counting examples are very useful, and I'll often use AtomicIntegers for counting and VM-wide unique generators if there's any hint of multiple threads being involved, because they're so easy to work with I'd almost consider it premature optimisation to use plain ints.

While you can almost always achieve the same synchronization guarantees with ints and appropriate synchronized declarations, the beauty of AtomicInteger is that the thread-safety is built into the actual object itself, rather than you needing to worry about the possible interleavings, and monitors held, of every method that happens to access the int value. It's much harder to accidentally violate threadsafety when calling getAndIncrement() than when returning i++ and remembering (or not) to acquire the correct set of monitors beforehand.

Pattani answered 27/1, 2011 at 16:11 Comment(4)
Thanks for this clear explanation. What would be the advantages of using an AtomicInteger over a class where methods are all synchronized? Would the latter be considered as "heavier"?Autoionization
From my perspective, it's mainly the encapsulation you get with AtomicIntegers - synchronization happens on exactly what you need, and you get descriptive methods that are in the public API to explain what the intended result is. (Plus to some extent you're right, often one would end up simply synchronizing all methods in a class which is likely too coarse-grained, though with HotSpot performing lock optimisations and the rules against premature optimisation, I consider the readability to be a greater benefit than performance.)Pattani
This is very clear and precise explaination, Thanks !!Cheder
Finally an explanation that properly cleared it up for me.Eberta
C
71

If you look at the methods AtomicInteger has, you'll notice that they tend to correspond to common operations on ints. For instance:

static AtomicInteger i;

// Later, in a thread
int current = i.incrementAndGet();

is the thread-safe version of this:

static int i;

// Later, in a thread
int current = ++i;

The methods map like this:
++i is i.incrementAndGet()
i++ is i.getAndIncrement()
--i is i.decrementAndGet()
i-- is i.getAndDecrement()
i = x is i.set(x)
x = i is x = i.get()

There are other convenience methods as well, like compareAndSet or addAndGet

Concede answered 27/1, 2011 at 16:37 Comment(0)
M
44

The primary use of AtomicInteger is when you are in a multithreaded context and you need to perform thread safe operations on an integer without using synchronized. The assignation and retrieval on the primitive type int are already atomic but AtomicInteger comes with many operations which are not atomic on int.

The simplest are the getAndXXX or xXXAndGet. For instance getAndIncrement() is an atomic equivalent to i++ which is not atomic because it is actually a short cut for three operations: retrieval, addition and assignation. compareAndSet is very useful to implements semaphores, locks, latches, etc.

Using the AtomicInteger is faster and more readable than performing the same using synchronization.

A simple test:

public synchronized int incrementNotAtomic() {
    return notAtomic++;
}

public void performTestNotAtomic() {
    final long start = System.currentTimeMillis();
    for (int i = 0 ; i < NUM ; i++) {
        incrementNotAtomic();
    }
    System.out.println("Not atomic: "+(System.currentTimeMillis() - start));
}

public void performTestAtomic() {
    final long start = System.currentTimeMillis();
    for (int i = 0 ; i < NUM ; i++) {
        atomic.getAndIncrement();
    }
    System.out.println("Atomic: "+(System.currentTimeMillis() - start));
}

On my PC with Java 1.6 the atomic test runs in 3 seconds while the synchronized one runs in about 5.5 seconds. The problem here is that the operation to synchronize (notAtomic++) is really short. So the cost of the synchronization is really important compared to the operation.

Beside atomicity AtomicInteger can be use as a mutable version of Integer for instance in Maps as values.

Montalvo answered 27/1, 2011 at 16:21 Comment(10)
I don't think I'd want to use AtomicInteger as a map key, because it uses the default equals() implementation, which is almost certainly not what you'd expect the semantics to be if used in a map.Pattani
@Andrzej sure, not as key which are required to be unmutable but a value.Montalvo
@Montalvo Any idea why atomic integer performs well over synchronized?Sikang
The test are quite old now (more than 6 years) it might me interesting to retest with a recent JRE. I didn't went deep enough in the AtomicInteger to answer but as this is a very specific task it'll use synchronization techniques that are only working in this specific case. Also mind that the test is monothreaded and making a similar test in a heaviliy loaded environment might not give such a clear victory for AtomicIntegerMontalvo
I believe its 3 ms and 5.5 msJulide
I don't remember as it's has been done more that 7 years ago but as I put calls in the loop I guess I probably set NUM high enough to last a couple of seconds.Montalvo
@SupunWijerathne: This only tested the no-contention case where no other threads ever take the lock. I wouldn't be surprised if that JVM version used an atomic RMW both to take the lock (for synchronized) and to release it. Although in that case you'd expect exactly a factor of 2, assuming a typical x86 where lock add [mem], 1 is the same latency as xchg [mem], reg or lock cmpxchg [mem], reg. (uops.info). Maybe just the extra store/reload of the non-atomic ++ between atomic RMWs and release-store to unlock were a factor.Arlettearley
(Assuming of course that the atomic RMW JITed to a loop with no memory accesses except for lock add [mem], 1, or maybe lock xadd [mem], eax if the JIT doesn't take advantage of the return value being unused.)Arlettearley
If this had been a multi-threaded test: Threads have to contend for a lock, and then after getting ownership of the lock, actually access the int which is probably in a different cache line. The store part of the notAtomic++ increment has to commit to cache before it can unlock the lock. So it wouldn't be surprising to nearly doubles the latency from getting exclusive ownership to releasing exclusive ownership. Or worse, since some threads would ping-pong the cache line to themselves without getting lock ownership, delaying unlock.Arlettearley
In a real-world case where you have multiple threads all doing not much besides hammering on a shared counter, 1. your design sucks, a single thread with non-atomic variables would probably be faster, even if it did have to write-only store each incremented value to its L1d cache. 2. yes, AtomicInteger is probably faster since each hardware step succeeds at doing an increment. (Except sometimes with LL/SC machines, unlike x86).Arlettearley
G
21

For example, I have a library that generates instances of some class. Each of these instances must have a unique integer ID, as these instances represent commands being sent to a server, and each command must have a unique ID. Since multiple threads are allowed to send commands concurrently, I use an AtomicInteger to generate those IDs. An alternative approach would be to use some sort of lock and a regular integer, but that's both slower and less elegant.

Grillroom answered 27/1, 2011 at 16:13 Comment(1)
Thanks for sharing this practical example. This sounds like something I should use as I need to have unique id for each file I import into my program :)Autoionization
E
8

In Java 8 atomic classes have been extended with two interesting functions:

  • int getAndUpdate(IntUnaryOperator updateFunction)
  • int updateAndGet(IntUnaryOperator updateFunction)

Both are using the updateFunction to perform update of the atomic value. The difference is that the first one returns old value and the second one return the new value. The updateFunction may be implemented to do more complex "compare and set" operations than the standard one. For example it can check that atomic counter doesn't go below zero, normally it would require synchronization, and here the code is lock-free:

    public class Counter {

      private final AtomicInteger number;

      public Counter(int number) {
        this.number = new AtomicInteger(number);
      }

      /** @return true if still can decrease */
      public boolean dec() {
        // updateAndGet(fn) executed atomically:
        return number.updateAndGet(n -> (n > 0) ? n - 1 : n) > 0;
      }
    }

The code is taken from Java Atomic Example.

Elbertelberta answered 3/12, 2015 at 9:20 Comment(0)
U
7

Like gabuzo said, sometimes I use AtomicIntegers when I want to pass an int by reference. It's a built-in class that has architecture-specific code, so it's easier and likely more optimized than any MutableInteger I could quickly code up. That said, it feels like an abuse of the class.

Unipolar answered 6/12, 2011 at 6:35 Comment(0)
W
7

I usually use AtomicInteger when I need to give Ids to objects that can be accesed or created from multiple threads, and i usually use it as an static attribute on the class that i access in the constructor of the objects.

Waterfront answered 26/3, 2013 at 19:1 Comment(0)
E
4

You can implement non-blocking locks using compareAndSwap (CAS) on atomic integers or longs. The "Tl2" Software Transactional Memory paper describes this:

We associate a special versioned write-lock with every transacted memory location. In its simplest form, the versioned write-lock is a single word spinlock that uses a CAS operation to acquire the lock and a store to release it. Since one only needs a single bit to indicate that the lock is taken, we use the rest of the lock word to hold a version number.

What it is describing is first read the atomic integer. Split this up into an ignored lock-bit and the version number. Attempt to CAS write it as the lock-bit cleared with the current version number to the lock-bit set and the next version number. Loop until you succeed and your are the thread which owns the lock. Unlock by setting the current version number with the lock-bit cleared. The paper describes using the version numbers in the locks to coordinate that threads have a consistent set of reads when they write.

This article describes that processors have hardware support for compare and swap operations making the very efficient. It also claims:

non-blocking CAS-based counters using atomic variables have better performance than lock-based counters in low to moderate contention

Eanes answered 17/9, 2012 at 20:20 Comment(0)
P
3

The key is that they allow concurrent access and modification safely. They're commonly used as counters in a multithreaded environment - before their introduction this had to be a user written class that wrapped up the various methods in synchronized blocks.

Perversity answered 27/1, 2011 at 16:10 Comment(1)
I see. Is this in cases where an attribute or instance acts as a sort of global variable inside an application. Or are there other cases that you can think of?Autoionization
P
2

I used AtomicInteger to solve the Dining Philosopher's problem.

In my solution, AtomicInteger instances were used to represent the forks, there are two needed per philosopher. Each Philosopher is identified as an integer, 1 through 5. When a fork is used by a philosopher, the AtomicInteger holds the value of the philosopher, 1 through 5, otherwise the fork is not being used so the value of the AtomicInteger is -1.

The AtomicInteger then allows to check if a fork is free, value==-1, and set it to the owner of the fork if free, in one atomic operation. See code below.

AtomicInteger fork0 = neededForks[0];//neededForks is an array that holds the forks needed per Philosopher
AtomicInteger fork1 = neededForks[1];
while(true){    
    if (Hungry) {
        //if fork is free (==-1) then grab it by denoting who took it
        if (!fork0.compareAndSet(-1, p) || !fork1.compareAndSet(-1, p)) {
          //at least one fork was not succesfully grabbed, release both and try again later
            fork0.compareAndSet(p, -1);
            fork1.compareAndSet(p, -1);
            try {
                synchronized (lock) {//sleep and get notified later when a philosopher puts down one fork                    
                    lock.wait();//try again later, goes back up the loop
                }
            } catch (InterruptedException e) {}

        } else {
            //sucessfully grabbed both forks
            transition(fork_l_free_and_fork_r_free);
        }
    }
}

Because the compareAndSet method does not block, it should increase throughput, more work done. As you may know, the Dining Philosophers problem is used when controlled accessed to resources is needed, i.e. forks, are needed, like a process needs resources to continue doing work.

Photocopier answered 22/12, 2018 at 2:26 Comment(0)
W
0

Simple example for compareAndSet() function:

import java.util.concurrent.atomic.AtomicInteger; 

public class GFG { 
    public static void main(String args[]) 
    { 

        // Initially value as 0 
        AtomicInteger val = new AtomicInteger(0); 

        // Prints the updated value 
        System.out.println("Previous value: "
                           + val); 

        // Checks if previous value was 0 
        // and then updates it 
        boolean res = val.compareAndSet(0, 6); 

        // Checks if the value was updated. 
        if (res) 
            System.out.println("The value was"
                               + " updated and it is "
                           + val); 
        else
            System.out.println("The value was "
                               + "not updated"); 
      } 
  } 

The printed is: previous value: 0 The value was updated and it is 6 Another simple example:

    import java.util.concurrent.atomic.AtomicInteger; 

public class GFG { 
    public static void main(String args[]) 
    { 

        // Initially value as 0 
        AtomicInteger val 
            = new AtomicInteger(0); 

        // Prints the updated value 
        System.out.println("Previous value: "
                           + val); 

         // Checks if previous value was 0 
        // and then updates it 
        boolean res = val.compareAndSet(10, 6); 

          // Checks if the value was updated. 
          if (res) 
            System.out.println("The value was"
                               + " updated and it is "
                               + val); 
        else
            System.out.println("The value was "
                               + "not updated"); 
    } 
} 

The printed is: Previous value: 0 The value was not updated

Withe answered 7/11, 2019 at 17:8 Comment(0)
S
0

Atomic classes are not general purpose replacements for java.lang.Integer and related classes. They do not define methods such as equals, hashCode and compareTo. (Because atomic variables are expected to be mutated, they are poor choices for hash table keys.) Additionally, classes are provided only for those types that are commonly useful in intended applications. For example, there is no atomic class for representing byte. In those infrequent cases where you would like to do so, you can use an AtomicInteger to hold byte values, and cast appropriately. You can also hold floats using Float.floatToRawIntBits(float) and Float.intBitsToFloat(int) conversions, and doubles using Double.doubleToRawLongBits(double) and Double.longBitsToDouble(long) conversions.

Reference: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html

Semiology answered 14/6, 2022 at 3:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.