A teammate uses a `volatile int` and writes `count++` for a shared request counter, expecting it to be thread-safe. Is it? Explain and fix it.
answer
- volatile = visibility (and single-access atomicity), NOT compound atomicity
- count++ = read + add + write → three steps → lost update
- AtomicInteger.incrementAndGet() does the RMW atomically (CAS loop)
- volatile is fine for a set-by-one/read-by-many flag, not for ++
- Alternatives: synchronized block, or LongAdder for hot counters
basics
~10 sNo. volatile makes the value visible across threads but count++ is read-then-add-then-write, three steps, so two threads can both read the same number and lose an update. Fix it with an AtomicInteger and incrementAndGet().
solid answer
~50 sIt is not thread-safe. `volatile` guarantees *visibility* — every thread sees the latest written value — and makes a single read or write of the int atomic. But `count++` is a *compound* read-modify-write: read `count`, add 1, write it back. Two threads can both read the same value (say 5), both compute 6, and both write 6, so one increment is lost. `volatile` doesn't make those three steps indivisible; it only fixes visibility, not the race. The right fix is `AtomicInteger` with `incrementAndGet()`, which performs the read-modify-write as one atomic compare-and-swap operation (with a retry loop) and has the same visibility guarantees as volatile. Alternatives are a `synchronized` block around the increment, or `LongAdder` if the counter is extremely hot. Plain `volatile int` is correct only for a flag that one thread *sets* and others *read* — never for `++`.
code
java · 8 lines// BROKEN: volatile gives visibility, not atomic increment.
// private volatile int count;
// void onRequest() { count++; } // lost updates under concurrency
// FIXED: atomic read-modify-write via CAS.
private final AtomicInteger count = new AtomicInteger();
void onRequest() { count.incrementAndGet(); }
int current() { return count.get(); }go deeper
Recognizes that count++ on a shared field is not thread-safe even with volatile, explains it's three steps, and fixes it with AtomicInteger.incrementAndGet().
Clearly separates visibility (what volatile gives) from compound atomicity (what it doesn't), describes the lost-update interleaving, and knows volatile is appropriate for a simple flag.
Adds that AtomicInteger uses CAS and carries volatile-strength visibility, knows when synchronized or LongAdder is the better fit, and can reason about the publication use of volatile for immutable objects.
Frames it via the Java Memory Model (happens-before, atomicity vs visibility), advises team conventions on volatile-for-flags vs atomics-for-counters, and connects to safe publication and contention trade-offs.
## What `volatile` actually promises Marking a field `volatile` gives two guarantees: 1. **Visibility.** A write to a volatile field is immediately visible to every other thread's next read — no thread keeps a stale cached copy. (Reads/writes also can't be reordered across the access in ways that break this.) 2. **Atomic single access.** A single *read* or single *write* of the field happens as one indivisible step (this also makes 64-bit `long`/`double` reads/writes atomic, which they otherwise aren't). What `volatile` does **not** promise: that a *sequence* of operations on the field is indivisible. ## Why `count++` is still a race `count++` looks like one operation but the CPU executes three: ``` 1. read count (load current value) 2. add 1 (compute value + 1) 3. write count (store the result) ``` This is a **read-modify-write (RMW)** sequence. With `volatile`, each individual load and store is atomic and visible — but nothing stops another thread from slipping *between* step 1 and step 3. Interleaving that loses an update: ``` count == 5 Thread A: read 5 Thread B: read 5 Thread A: write 6 Thread B: write 6 <- B also computed 5+1, overwrites A's 6 Result: count == 6, but TWO increments happened. One is lost. ``` No amount of `volatile` fixes this, because the bug isn't about visibility — both threads *did* see 5 correctly — it's that the three steps aren't grouped into one atomic unit. ## The correct fixes **1. `AtomicInteger` (preferred for a counter).** Its `incrementAndGet()` performs the whole RMW atomically using compare-and-swap with an internal retry loop, and carries volatile-strength visibility: ```java private final AtomicInteger count = new AtomicInteger(); void onRequest() { count.incrementAndGet(); } int current() { return count.get(); } ``` **2. `synchronized` (or a `Lock`).** Make the increment a critical section so only one thread runs it at a time: ```java private int count; synchronized void onRequest() { count++; } synchronized int current() { return count; } // read must also be synchronized for visibility ``` **3. `LongAdder`** for an extremely hot counter with many writers — it stripes updates across cells to reduce contention; read the total with `sum()`. ## When is `volatile` the right tool? `volatile` is correct for a **simple flag or published reference** where the operation is a *plain assignment*, not a compound update — one thread writes, others read: ```java private volatile boolean running = true; // one thread sets false to stop others ``` Here there is no read-modify-write, just a single write and single reads, so visibility is all you need and `volatile` suffices. The moment you do `x++`, `x += n`, or "read, decide, write," you've left `volatile`'s territory and need an atomic or a lock. ## One-line takeaway `volatile` fixes *visibility*; it does **not** make a compound `++` *atomic*. For a shared counter, use `AtomicInteger.incrementAndGet()`.
- So when IS a plain `volatile` field the correct choice?When the only operations are a single write by some thread(s) and single reads by others — a status flag like `volatile boolean shutdown` or publishing an immutable object via `volatile MyConfig config`. There's no read-modify-write, so visibility (what volatile provides) is exactly enough. As soon as you need ++ or any read-decide-write, switch to an atomic or a lock.
- Does AtomicInteger also guarantee visibility, or only atomicity?Both. AtomicInteger's operations have the same memory-visibility (happens-before) guarantees as volatile, so a value set by one thread is seen by another, AND they make the read-modify-write atomic via CAS. So it's a strict superset of what volatile gives for an int counter.
saying these in an interview costs you the question
- Saying volatile makes count++ atomic / thread-safe
- Confusing visibility with atomicity (volatile gives the former, not compound atomicity)
- Thinking AtomicInteger is just a volatile int (it adds atomic read-modify-write via CAS)
- Synchronizing only the increment but not the read, then wondering about stale reads
- Reaching for synchronized when a single AtomicInteger is simpler for a plain counter