Of course! Let's dive deep into java.util.concurrent.atomic.AtomicInteger.

What is AtomicInteger?
AtomicInteger is a class in Java's java.util.concurrent.atomic package. It provides an int value that can be updated atomically.
"Atomically" is the key word here. It means that a series of operations (like reading, modifying, and writing) are performed as a single, indivisible unit. Once an atomic operation starts, it cannot be interrupted by other threads. This guarantees that the operation completes in its entirety before any other thread can access the shared variable.
Why Do We Need It?
In multi-threaded applications, when multiple threads read and write to a shared primitive variable (like int), you face classic concurrency problems like race conditions.
Consider this simple, non-atomic code:

class Counter {
private int count = 0;
public void increment() {
count++; // This is NOT an atomic operation!
}
public int getCount() {
return count;
}
}
The count++ statement might look like a single action, but it's actually three separate operations:
- Read the current value of
count. - Increment that value.
- Write the new value back to
count.
If two threads (Thread A and Thread B) try to execute increment() at the same time, this is a possible scenario:
- Thread A reads
count(value is0). - Thread B reads
count(value is still0). - Thread A increments its read value (
0 + 1 = 1). - Thread B increments its read value (
0 + 1 = 1). - Thread A writes
1back tocount. - Thread B writes
1back tocount.
The Result: The count is now 1, but it should have been 2. This is a race condition.
AtomicInteger solves this problem by ensuring that read-modify-write operations are performed atomically, preventing such race conditions.

How AtomicInteger Works: The "Magic"
AtomicInteger uses a sophisticated technique called Compare-And-Swap (CAS), which is a hardware-level instruction supported by most modern processors.
Here's a simplified view of how a CAS operation works:
- Read: Get the current value of the variable from memory (let's call it
V). - Compare: Compare
Vwith an expected value (let's call itE). The CAS operation succeeds only ifVis equal toE. - Swap: If they are equal, atomically update the variable's value in memory to a new value (let's call it
N). If they are not equal, do nothing and let the caller try again.
This entire read-compare-swap sequence is a single, non-interruptible hardware instruction.
Let's see how AtomicInteger's incrementAndGet() would work using CAS:
- Thread A wants to increment. It reads the current value
V(e.g.,5). - It sets its expected value
Eto5and the new valueNto6. - It performs a CAS operation: "If the value in memory is
5, set it to6." - The hardware checks the memory. The value is indeed
5. It's updated to6. Thread A's operation succeeds. - Meanwhile, Thread B also wants to increment. It reads the current value
V(which is now6). - It sets its expected value
Eto6and the new valueNto7. - It performs a CAS operation: "If the value in memory is
6, set it to7." - The hardware checks the memory. The value is
6. It's updated to7. Thread B's operation succeeds.
The Key Advantage: CAS is lock-free. Unlike traditional synchronized blocks, which can block threads, CAS allows threads to keep trying (looping) until they successfully update the variable. This can lead to better performance under high contention, as it avoids the overhead of thread context switching.
Common Methods and Examples
Here are the most frequently used methods in AtomicInteger.
Basic Get and Set
These methods are straightforward and thread-safe.
AtomicInteger atomicInt = new AtomicInteger(10); // Get the current value int currentValue = atomicInt.get(); // currentValue is 10 // Set a new value atomicInt.set(20); System.out.println(atomicInt.get()); // Prints 20
Atomic "Read-Modify-Write" Operations
These are the most powerful methods, as they guarantee atomicity.
AtomicInteger atomicInt = new AtomicInteger(5); // Atomically increments by 1 and returns the new value int newValue1 = atomicInt.incrementAndGet(); // newValue1 is 6, atomicInt is 6 // Atomically decrements by 1 and returns the new value int newValue2 = atomicInt.decrementAndGet(); // newValue2 is 5, atomicInt is 5 // Atomically adds the given value and returns the new value int newValue3 = atomicInt.addAndGet(10); // newValue3 is 15, atomicInt is 15
Conditional Updates (The CAS Core)
These methods attempt an update and return a boolean indicating success or failure.
AtomicInteger atomicInt = new AtomicInteger(10);
// Atomically sets the value to 15 if the current value is 10
boolean success = atomicInt.compareAndSet(10, 15);
System.out.println("Update successful? " + success); // Prints true
System.out.println("Current value: " + atomicInt.get()); // Prints 15
// Try to set it to 20, but the current value is 15, not 10
boolean failure = atomicInt.compareAndSet(10, 20);
System.out.println("Update successful? " + failure); // Prints false
System.out.println("Current value: " + atomicInt.get()); // Prints 15 (unchanged)
Lazy-Set (for Performance)
lazySet() is a performance optimization. It sets the value without enforcing any happens-before ordering with subsequent reads. This is useful in scenarios where you don't need other threads to see the update immediately, reducing memory visibility overhead.
atomicInt.lazySet(100); // Sets the value, but a read might not see it immediately.
The Atomic vs. synchronized Example
Let's see AtomicInteger in action in our Counter class.
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
class AtomicCounter {
// The shared counter is now an AtomicInteger
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
// This operation is atomic and thread-safe
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
}
public class AtomicIntegerDemo {
public static void main(String[] args) throws InterruptedException {
AtomicCounter counter = new AtomicCounter();
int threadCount = 1000;
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
// Launch 1000 threads, each incrementing the counter 1000 times
for (int i = 0; i < threadCount; i++) {
executor.submit(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment();
}
});
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
System.out.println("Final count: " + counter.getCount());
// The output will be exactly 1,000,000. No race condition!
}
}
When to Use AtomicInteger vs. synchronized
| Feature | AtomicInteger |
synchronized |
|---|---|---|
| Granularity | Fine-grained. Operates on a single variable. | Coarse-grained. Can protect a block of code or an entire method. |
| Performance | Generally better under low to moderate contention. Lock-free. | Can be more efficient under high contention for complex operations, as it avoids the "retry" loop of CAS. |
| Use Case | Perfect for simple, independent atomic operations on a single primitive or reference variable (AtomicBoolean, AtomicLong, AtomicReference). |
Best for protecting a sequence of multiple operations that must be executed together without interference. |
| Readability | Can be less intuitive for complex logic. | Very clear and explicit about the protected section of code. |
Summary
AtomicIntegerprovides a thread-safeintwrapper.- It uses Compare-And-Swap (CAS) to achieve atomicity without traditional locks, making it lock-free.
- It's ideal for performing simple, atomic operations like increment, decrement, and conditional updates on
