Hook: Interviewers love AtomicInteger because it tests whether you know how to update shared state safely without accidentally turning your multithreaded code into a traffic jam.
Question: What is AtomicInteger in Java?
Answer: AtomicInteger is a thread-safe integer wrapper from java.util.concurrent.atomic. It lets multiple threads read and update the same number without using a full synchronized block for every change. The main benefit is atomic operations like incrementAndGet() and compareAndSet(), which means the update happens as one indivisible action.
Interview-Ready Answer: I use AtomicInteger when I need a shared counter or state value that many threads update concurrently. It gives me lock-free atomic operations such as get(), set(), and compareAndSet(), so I avoid race conditions without paying the heavier cost of a synchronized lock on every increment. A key detail is that it is great for simple numeric state, but for more complex multi-field invariants I would usually still need locks or another coordination strategy.
AtomicInteger is an object that stores one int and provides atomic methods to read, write, and update it. Atomic means “all at once” from the perspective of other threads: no other thread can observe a half-finished update. It lives in java.util.concurrent.atomic and is built on low-level CPU support and JVM mechanics such as compare-and-swap, often shortened to CAS (compare-and-swap, a hardware-assisted “update only if the value is still what I expected” operation).
5.6.5 to 6 only if nobody changed it first.”This retry loop is why atomic classes are often called lock-free: they do not use an OS-style lock for the basic update. However, “lock-free” does not mean “free” — under heavy contention, many retries can happen, and performance can drop.
Use AtomicInteger when you need a single shared number such as:
It is ideal when the operation is simple and local to one variable. If you need to update several fields together as one rule, such as “balance and transaction count must always match,” an atomic integer alone is not enough.
| Option | Best for | Trade-off |
|---|---|---|
AtomicInteger | Single shared counter | Great for simple updates; contention can hurt |
synchronized | Multi-step invariants | Simple and safe, but blocks threads |
volatile int | Visibility only | Reads/writes are visible, but i++ is not atomic |
LongAdder | Very hot counters | Faster under contention, but weaker for exact instant reads |
volatile is a common trap: it makes changes visible across threads, but it does not make compound actions like increment atomic. The expression count++ is really read, add, write — three separate steps — so two threads can overwrite each other.
For a successful atomic update, the average work is usually O(1), but under contention the retry loop can make it feel much slower because failed CAS attempts repeat work. In real systems, a low-contention atomic counter may be extremely fast, while a very hot counter with dozens of threads can become a bottleneck. That is where LongAdder often wins, because it spreads updates across multiple cells and combines them later.
if (atomic.get() < limit) atomic.incrementAndGet(); is not atomic as a whole. Another thread can change the value between the check and the increment.0 if you use the no-arg constructor.Memory hook: think of AtomicInteger as a bulletproof ticket counter: each thread can only stamp the next number if nobody else grabbed that ticket first.
Imagine a checkout service in an e-commerce platform that records how many orders were placed in the last minute. Many request threads call the same metric counter. If the team used a plain int with ++, the count would randomly drift lower than reality because updates get lost during races. With AtomicInteger, each order increments safely, so dashboards and alerting stay accurate.
What goes wrong when misunderstood: A team uses volatile int for “thread safety” and sees counter values that are lower than the true number of requests. Logs show normal traffic, but the dashboard under-reports load, causing autoscaling to trigger late. Users then experience slow responses during peak traffic because the system thought it was less busy than it really was.
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerDemo {
private static final int THREADS = 8;
private static final int INCREMENTS_PER_THREAD = 100_000;
public static void main(String[] args) throws InterruptedException {
AtomicInteger counter = new AtomicInteger(0);
CountDownLatch startGate = new CountDownLatch(1);
CountDownLatch doneGate = new CountDownLatch(THREADS);
for (int t = 0; t < THREADS; t++) {
Thread worker = new Thread(() -> {
try {
// All threads wait here so the race is real and reproducible.
startGate.await();
for (int i = 0; i < INCREMENTS_PER_THREAD; i++) {
counter.incrementAndGet();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
doneGate.countDown();
}
}, "worker-" + t);
worker.start();
}
long start = System.nanoTime();
startGate.countDown();
doneGate.await();
long elapsedMillis = (System.nanoTime() - start) / 1_000_000;
int expected = THREADS * INCREMENTS_PER_THREAD;
System.out.println("AtomicInteger final value = " + counter.get());
System.out.println("Expected value = " + expected);
System.out.println("Elapsed ms = " + elapsedMillis);
// Edge case: compareAndSet only succeeds if the current value is exactly what we expect.
boolean updated = counter.compareAndSet(expected, 12345);
System.out.println("compareAndSet success = " + updated);
System.out.println("Value after CAS = " + counter.get());
// Failure path: this should fail because the current value is no longer 'expected'.
boolean wrongUpdate = counter.compareAndSet(expected, 99999);
System.out.println("compareAndSet with wrong expected value = " + wrongUpdate);
System.out.println("Value after failed CAS = " + counter.get());
// Important teaching point: a check-then-act sequence is not automatically atomic.
AtomicInteger limit = new AtomicInteger(1);
if (limit.get() < 2) {
// In real concurrent code, another thread could change 'limit' between the get() and this increment.
limit.incrementAndGet();
}
System.out.println("Limit value after check-then-act = " + limit.get());
}
}
Follow-up & Tricky Questions:
AtomicInteger different from volatile int? volatile gives visibility, meaning other threads see the latest write, but it does not make ++ atomic. AtomicInteger gives both visibility and atomic update methods.LongAdder instead? Use LongAdder for extremely hot counters with lots of concurrent increments, such as request metrics. It usually scales better under contention, but exact point-in-time reads are less straightforward than with AtomicInteger.AtomicInteger always faster than synchronized? No. Under low contention, both can be fine; under high contention, atomics avoid blocking, but repeated CAS retries can still become expensive. The real answer depends on workload and contention level.compareAndSet actually help with? It lets you implement safe “update only if unchanged” logic, which is the basis of many non-blocking algorithms. It is especially useful when you need optimistic updates and want to retry rather than block.count++ on an AtomicInteger atomic? Yes, if you use incrementAndGet() or getAndIncrement(). But writing counter.set(counter.get() + 1) is not atomic because it splits the operation into two separate calls.AtomicInteger solve all race conditions? No. It solves races for one integer value only. You can still have races around surrounding business logic or across multiple fields.Common Mistakes:
volatile int for counters. Correction: Use AtomicInteger or another atomic/concurrent counter because ++ is not atomic.LongAdder or redesign to reduce sharing.Memory Hook: AtomicInteger is a turnstile with one seat: one thread updates the number at a time, and everyone else retries until they get through safely.
Cheat Sheet:
AtomicInteger = thread-safe wrapper around one int.incrementAndGet() and compareAndSet() are the key methods.volatile is not enough for ++.LongAdder may scale better for very hot metrics.Practice Tasks:
AtomicInteger and verify the final count.AtomicInteger with volatile int and observe the wrong result under concurrency.compareAndSet.