Of course! Let's dive deep into AtomicInteger in Java.

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, meaning it's thread-safe without requiring explicit synchronization.
Think of it as a thread-safe version of the primitive int or the Integer wrapper class. Operations like increment, decrement, and compare-and-set are guaranteed to be performed as a single, indivisible step, even when multiple threads are accessing it concurrently.
The Core Problem it Solves: Race Conditions
Imagine you have a simple counter shared between multiple threads:
// NOT THREAD-SAFE CODE
class Counter {
private int count = 0;
public void increment() {
count++; // This is NOT an atomic operation!
}
public int getCount() {
return count;
}
}
The line count++ looks simple, but it's actually three separate operations:

- Read the current value of
count. - Increment that value by 1.
- Write the new value back to
count.
If two threads (Thread A and Thread B) read the value at the same time, they both see 0. They both increment to 1, and both write 1 back. The result is 1, but it should have been 2. This is a race condition.
AtomicInteger solves this by making the entire "read-modify-write" sequence atomic.
Key Methods and How They Work
The power of AtomicInteger comes from its methods that use low-level, hardware-supported instructions (like Compare-And-Swap, or CAS) to guarantee atomicity.
Basic Atomic Operations
int get(): Gets the current value.void set(int newValue): Sets the value tonewValue.int getAndIncrement(): Atomically increments the current value and returns the old value.int incrementAndGet(): Atomically increments the current value and returns the new value.int getAndDecrement(): Atomically decrements the current value and returns the old value.int decrementAndGet(): Atomically decrements the current value and returns the new value.int getAndAdd(int delta): Atomically adds the given value to the current value and returns the old value.int addAndGet(int delta): Atomically adds the given value to the current value and returns the new value.
Example:

import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerExample {
public static void main(String[] args) {
AtomicInteger atomicInt = new AtomicInteger(10);
System.out.println("Initial value: " + atomicInt.get()); // 10
// Atomically increments and returns the new value
int newValue = atomicInt.incrementAndGet();
System.out.println("After incrementAndGet: " + newValue); // 11
// Atomically adds 5 and returns the old value
int oldValue = atomicInt.getAndAdd(5);
System.out.println("Old value before addAndGet(5): " + oldValue); // 11
System.out.println("New value after addAndGet(5): " + atomicInt.get()); // 16
}
}
The "King" of Atomic Operations: compareAndSet (CAS)
This is the most powerful and fundamental method in AtomicInteger. It's the engine behind most of its other methods.
boolean compareAndSet(int expect, int update): Atomically sets the value toupdateif the current value is equal toexpect. It returnstrueif the update was successful, andfalseotherwise.
This is an optimistic lock. It says, "I expect the value to be X. If it is, I'll change it to Y. If another thread changed it first, I'll get false and can try again."
Analogy:
Imagine you're reserving a book at a library. You go to the librarian and say, "I want to reserve Atomic Habits (expect), and I'll take the last copy (update)." The librarian checks the shelf. If the book is there, they give it to you and mark it as reserved (returns true). If someone else just took the last copy a second ago, the librarian tells you it's gone (returns false), and you have to decide what to do next.
Example:
import java.util.concurrent.atomic.AtomicInteger;
public class CasExample {
public static void main(String[] args) {
AtomicInteger atomicInt = new AtomicInteger(100);
// Thread 1: Tries to set the value to 101 if it's currently 100
boolean success1 = atomicInt.compareAndSet(100, 101);
System.out.println("CAS 1 success? " + success1); // true
System.out.println("Current value: " + atomicInt.get()); // 101
// Thread 2: Tries to set the value to 102 if it's currently 100 (it's not)
boolean success2 = atomicInt.compareAndSet(100, 102);
System.out.println("CAS 2 success? " + success2); // false
System.out.println("Current value is still: " + atomicInt.get()); // 101
}
}
Other Useful Methods
int getAndUpdate(UnaryOperator<Integer> updateFunction): Atomically updates the current value with the results of applying the given function.int updateAndGet(UnaryOperator<Integer> updateFunction): Atomically updates the current value with the results of applying the given function and returns the new value.int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction): Atomically updates the current value by applying the given function to the current and given values, returning the previous value.int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction): Atomically updates the current value by applying the given function to the current and given values, returning the new value.
These are modern, functional-style alternatives to the getAndAdd style methods.
When to Use AtomicInteger?
You should use AtomicInteger when you have a single mutable variable that is shared across multiple threads and you need to perform simple atomic operations on it.
Good Use Cases:
- Counters: Counting the number of events processed by multiple threads.
- Generators: Generating unique sequence numbers or IDs (e.g.,
getAndIncrement()). - Simple Flags or States: Managing a state that can transition between a few integer values atomically.
Example: A Thread-Safe Counter
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class SafeCounter {
// The AtomicInteger ensures all operations on count are thread-safe
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
public static void main(String[] args) throws InterruptedException {
SafeCounter counter = new SafeCounter();
ExecutorService executor = Executors.newFixedThreadPool(10);
// Submit 1000 tasks to the counter
for (int i = 0; i < 1000; i++) {
executor.submit(counter::increment);
}
// Shutdown the executor and wait for tasks to finish
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
// The final count will be exactly 1000
System.out.println("Final count: " + counter.getCount()); // Correctly prints 1000
}
}
AtomicInteger vs. synchronized
| Feature | AtomicInteger |
synchronized block/method |
|---|---|---|
| Granularity | Fine-grained. Locks only the specific variable. | Coarse-grained. Locks an entire block of code or object. |
| Performance | Generally higher performance under low-to-medium contention because it uses non-blocking algorithms (CAS). | Can be slower under high contention as threads may block and wait for the lock. |
| Complexity | Simple for single-variable operations. Can become complex with compound logic (e.g., if-then-else on an atomic value). |
More straightforward for complex, multi-step operations. |
| Deadlocks | Cannot deadlock. | Can deadlock if not used carefully (e.g., acquiring multiple locks in different orders). |
| Use Case | Ideal for single, mutable primitive variables (counters, flags). | Ideal for protecting a critical section of code that involves multiple shared variables or complex logic. |
Conclusion
AtomicInteger is a powerful and highly efficient tool for building concurrent applications in Java. It provides a simple, thread-safe way to manage shared integer state without the overhead and complexity of traditional locks.
Key Takeaways:
- Use
AtomicIntegerfor simple, atomic operations on shared integer counters or flags. - Its core mechanism is
compareAndSet(CAS), a non-blocking, optimistic locking strategy. - It generally outperforms
synchronizedfor simple operations, especially when contention is low. - For complex logic involving multiple shared variables, a
synchronizedblock is often a clearer and safer choice.
