Search⌘ K
AI Features

AtomicInteger

Explore the AtomicInteger class to learn how it enables atomic read-modify-write operations without locks, improving thread performance. Understand its differences from int and discover how to simulate atomic byte and float types using AtomicInteger, enhancing your Java concurrency skills for interviews.

If you are interviewing, consider buying our number#1 course for Java Multithreading Interviews.

Overview

The AtomicInteger class represents an integer value that can be updated atomically, i.e. the read-modify-write operation can be executed atomically upon an instance of AtomicInteger. The class extends Number.

AtomicInteger makes for great counters as it uses the compare-and-swap (CAS) instruction under the hood which doesn’t penalize threads competing for access to the same data with suspension as locks do In general, suspension and resumption of threads involves significant overhead and under low to moderate contention non-blocking algorithms that use CAS outperform lock-based alternatives.

You can find more details on non-blocking synchronization and atomics here.

Performance

To demonstrate the performance of AtomicInteger we can construct a crude test, where a counter is incremented a million times by ten threads to reach a total of ten million. We’ll time the run for an AtomicInteger counter and an ordinary int counter. The widget below outputs the results:

Java
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
class Demonstration {
static AtomicInteger counter = new AtomicInteger(0);
static int simpleCounter = 0;
public static void main( String args[] ) throws Exception {
test(true);
test(false);
}
synchronized static void incrementSimpleCounter() {
simpleCounter++;
}
static void test(boolean isAtomic) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(10);
long start = System.currentTimeMillis();
try {
for (int i = 0; i < 10; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000000; i++) {
if (isAtomic) {
counter.incrementAndGet();
} else {
incrementSimpleCounter();
}
}
}
});
}
} finally {
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.HOURS);
}
long timeTaken = System.currentTimeMillis() - start;
System.out.println("Time taken by " + (isAtomic ? "atomic integer counter " : "integer counter ") + timeTaken + " milliseconds.");
}
}

Difference with int

Remember that AtomicInteger isn’t equivalent to int. Specifically, AtomicInteger class doesn’t override equals() or hashcode() and each instance is distinct. AtomicInteger can’t be used as a drop-in replacement for an int or Integer. This is demonstrated by the widget below:

Java
import java.util.HashMap;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
class Demonstration {
public static void main( String args[] ) {
// create map
HashMap<AtomicInteger, String> mapAtomic = new HashMap<>();
HashMap<Integer, String> mapInt = new HashMap<>();
// create two instances with the same value 5
AtomicInteger fiveAtomic = new AtomicInteger(5);
AtomicInteger fiveAtomicToo = new AtomicInteger(5);
// create two Integer instances
Integer fiveInt = new Integer(5);
Integer fiveIntToo = new Integer(5);
// Though the key is 5, but the two AtomicInteger instances
// have different hashcodes
mapAtomic.put(fiveAtomic, "first five atomic");
mapAtomic.put(fiveAtomicToo, "second five atomic");
System.out.println("value for key 5 : " + mapAtomic.get(fiveAtomic));
// With Integer type key, the second put overwrites the
// key with Integer value 5.
mapInt.put(fiveInt, "first five int");
mapInt.put(fiveIntToo, "second five int");
System.out.println("value for key 5 : " + mapInt.get(fiveInt));
}
}

Using AtomicInteger to simulate atomic byte

Primitive scalars are only available as AtomicInteger or AtomicLong but we can use AtomicInteger to create types that simulate atomic byte. The byte data type is an 8-bit signed two’s complement integer. It has a minimum value of -128 and a maximum value of 127 (inclusive).

In the following widget we create a class AtomicByte which internally uses an integer to store a byte representation. The class offers two methods shiftLeft() and shiftRight() that move the bit pattern by one bit left or right respectively in a thread-safe manner and without using locks. The listing appears with comments to explain the working of the class.

Java
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
class Demonstration {
public static void main( String args[] ) throws Exception {
AtomicByte atomicByte = new AtomicByte((byte) 0b11111111);
ExecutorService executorService = Executors.newFixedThreadPool(10);
// We'll create seven threads to shift our initial pattern of all 1s
// to the left by one. Eventually, we should see the pattern 10000000
// i.e. a single one followed by seven zeros. We create seven threads
// and each thread moves the pattern to the left by one.
try {
for (int i = 0; i < 7; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
atomicByte.shiftLeft();
}
});
}
} finally {
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.HOURS);
}
// print after the shift operations are complete. The
// result should be 10000000
atomicByte.print();
}
}

The class AtomicByte can be retrofitted with more fancy bit manipulation functionality if desired and without involving any locks.

Using AtomicInteger to simulate atomic float

An equivalent atomic class for float primitive type doesn’t exist, however, we can simulate one using AtomicInteger as we did for the primitive type byte. The float data type is a single-precision 32-bit IEEE 754 floating point. Its range of values is beyond the scope of this discussion. Float can be used instead of type double if you need to save memory in large arrays of floating point numbers. This data type should never be used for precise values, such as currency. For that, you will need to use the java.math.BigDecimal class instead.

To create our custom AtomicFloat type, we extend from the class Number and override several of its functions. Under the hood, we save the floating point’s bit representation in an instance of AtomicInteger. The class listing appears below with comments for explanation:

Java
import java.util.concurrent.atomic.AtomicInteger;
class AtomicFloat extends Number {
// integer to hold float bits
private AtomicInteger floatRepresentation = new AtomicInteger(0);
public AtomicFloat(float initialVal) {
floatRepresentation.set(Float.floatToIntBits(initialVal));
}
@Override
public int intValue() {
return (int) floatValue();
}
@Override
public long longValue() {
return (long) floatValue();
}
@Override
public float floatValue() {
return Float.intBitsToFloat(floatRepresentation.get());
}
@Override
public double doubleValue() {
return (double) floatValue();
}
public boolean compareAndSet(float expected, float newValue) {
return floatRepresentation.compareAndSet(Float.floatToIntBits(expected), Float.floatToIntBits(newValue));
}
public float getAndSet(float newValue) {
return floatRepresentation.getAndSet(Float.floatToIntBits(newValue));
}
public float getAndAdd(float delta) {
int currentVal;
int newVal;
do {
currentVal = floatRepresentation.get();
newVal = Float.floatToIntBits(Float.intBitsToFloat(currentVal) + delta);
} while (!floatRepresentation.compareAndSet(currentVal, newVal));
// Note that when we are returning the value of the float, it is possible that
// another thread updates the float value before the following line executes. The
// method as a whole doesn't execute atomically.
return Float.intBitsToFloat(floatRepresentation.get());
}
}

The AtomicByte and AtomicFloat classes in this lesson are shown for instructional purposes only. If you need to use these primitive types atomically, you can find well-written and well-tested libraries on the internet.