volatile vs Atomic* in Java — Visibility vs Compound Atomicity
Fast recall
Quick revision
Core answer
volatileCounter++ is still three operations (read, add, write) and can lose updates under concurrency. `AtomicInteger`, AtomicLong, etc., wrap a volatile field with lock-free CAS operations (compareAndSet) that make incrementAndGet(), addAndGet(), compareAndSet(expected, new), etc., atomic as a whole. Use `volatile` for simple flags (one writer, many readers) and double-checked-locking idioms. Use `AtomicInteger`/`Long` for counters and lock-free state machines. Use `LongAdder`/`LongAccumulator` for heavily contended counters (10–100× faster under contention). Use `AtomicReference` for compound state held as one object. When more than one variable must change atomically together, neither primitive is enough — use synchronized or StampedLock.Whether the candidate knows the atomicity gap in `volatile` (counter++ is broken), understands CAS as the Atomic* mechanism, and can pick the right tool per workload
Saying `volatile` makes `counter++` thread-safe — the #1 concurrency misconception. Not knowing `LongAdder` for contended counters
Mention that `AtomicLong` uses CAS (can livelock under extreme contention), `LongAdder` stripes counters across threads to reduce contention, and that `AtomicReference.updateAndGet(UnaryOperator)` gives lock-free state-machine updates
Interview-ready guide
Interview answer
volatile and Atomic* solve overlapping but different problems.
`volatile` gives you
- Visibility — writes immediately seen by other threads; no stale cached reads
- Ordering — no reordering across the volatile access; establishes happens-before
- Atomic single read or write of any primitive or reference, including
long/doublesince Java 5
`volatile` does NOT give you
- Compound operation atomicity
volatileCounter++is still three operations: read, add, write- Two threads can read the same value, both add 1, both write back the same number — one update is lost
- This is the most common concurrency bug in production Java code
`AtomicInteger` / `AtomicLong` give you everything volatile does, plus CAS
incrementAndGet,addAndGet,compareAndSet,updateAndGetare atomic as a whole- Internally: a
volatilefield + CPU-level Compare-And-Swap instruction (CMPXCHGon x86,LDREX/STREXon ARM) - Lock-free: threads spin on CAS retry rather than blocking on an OS mutex
- Fast under low-to-moderate contention; can livelock under extreme contention
`LongAdder` for heavily contended counters
- Stripes counters across per-thread cells
- Increments typically hit a thread-local cell — no contention
sum()aggregates when you read — a bit more expensive- Benchmarks on 16 cores:
AtomicLong~30M ops/sec,LongAdder~500M+ ops/sec - Micrometer uses
LongAdderfor its counter metric; that's the right default for rate-style counters
`AtomicReference.updateAndGet(UnaryOperator)` for lock-free state
- Holds an immutable state object
- Updates apply a function; CAS loop retries on contention
- Function must be pure (no side effects) because it can be retried
- Great for state machines where the state is a record with a few fields
When multiple fields must change together
- Neither
volatilenorAtomic*is enough - Need
synchronized,ReentrantLock, orStampedLock - Or bundle the fields into an immutable record and use
AtomicReference
Decision matrix
- Flag (running, shutdown): `volatile boolean`
- Single immutable reference publication: `volatile`
- Counter with moderate traffic: `AtomicLong`
- Counter with very high traffic: `LongAdder`
- Lock-free state machine on immutable state: `AtomicReference` + `updateAndGet`
- Multiple fields atomic together: `synchronized` or a lock
Recommendation: Never use volatile for a counter — always reach for AtomicLong or LongAdder. volatile is only for one-way flag-style publication. For metrics and rate counters, default to `LongAdder` — that's what Micrometer and most production metrics libraries use internally. For compound state that must change atomically across multiple fields, wrap it in an immutable record and use AtomicReference.updateAndGet, or go to synchronized / StampedLock. And remember that x86 hides many concurrency bugs that ARM doesn't — code correct under the JMM is portable; code that 'just works' on x86 may break on AWS Graviton or Apple Silicon.
`volatile` vs `Atomic*` in Java — Visibility vs Compound Atomicity
Direct Answer
volatile guarantees visibility and ordering for reads and writes of a single variable — but NOT atomicity of compound operations. volatileCounter++ is still three operations (read, add, write) and can lose updates under concurrency. AtomicInteger, AtomicLong, etc., wrap a volatile field with lock-free CAS operations (compareAndSet) that make incrementAndGet(), addAndGet(), compareAndSet(expected, new), etc., atomic as a whole. Use volatile for simple flags (one writer, many readers) and double-checked-locking idioms. Use AtomicInteger/Long for counters and lock-free state machines. Use LongAdder/LongAccumulator for heavily contended counters (10–100× faster under contention). Use AtomicReference for compound state held as one object. When more than one variable must change atomically together, neither primitive is enough — use synchronized or StampedLock.
Common Mistakes & Pitfalls
Saying volatile makes counter++ thread-safe — the #1 concurrency misconception. Not knowing LongAdder for contended counters
Check your understanding
Answer these without looking back at the article.
- Why is `volatile long counter; counter++` broken?
- How does `compareAndSet` work at the CPU level?
- When is `LongAdder` slower than `AtomicLong`?
- What's the JMM guarantee for `long` and `double` with and without `volatile`?
- How does `AtomicReference.updateAndGet` avoid race conditions?
Related interview prep categories
2Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.