skip to content

Atomic Variables

Lock-free updates built on compare-and-swap: the Atomic classes, the ABA hazard, high-contention adders, and the low-level VarHandle API. Interviewers use atomics to check whether you understand optimistic concurrency, not just locking.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

17

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.

level: juniorimportance: must knowfreq 70%

answer

  1. volatile = visibility (and single-access atomicity), NOT compound atomicity
  2. count++ = read + add + write → three steps → lost update
  3. AtomicInteger.incrementAndGet() does the RMW atomically (CAS loop)
  4. volatile is fine for a set-by-one/read-by-many flag, not for ++
  5. Alternatives: synchronized block, or LongAdder for hot counters

basics

~10 s

No. 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 s

It 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
java
// 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

for a junior

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().

for a middle

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.

for a senior

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.

for a principal

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

context

open as a page

What is the ABA problem in a CAS-based algorithm, and why can compare-and-swap miss it?

level: middleimportance: must knowfreq 62%

basics

~20 s

ABA is when a value goes A then B then back to A between a thread's read and its compare-and-swap. The CAS only checks the current value equals A, sees A, and succeeds, never noticing the change in between.

open as a page

What is compare-and-swap (CAS), and how do classes like AtomicInteger use it to update a value safely without locks?

level: middleimportance: must knowfreq 78%

basics

~20 s

CAS is an atomic operation that updates a value only if it still equals an expected old value. AtomicInteger uses it in a small retry loop, so threads can increment safely without taking a lock.

open as a page

What is LongAdder, and why might you prefer it over AtomicLong for a heavily-incremented counter?

level: middleimportance: should knowfreq 55%

basics

~20 s

LongAdder is a counter for many threads. Instead of one shared number, it keeps several internal cells so threads update different ones and rarely collide. You read the total with sum(). Under heavy concurrent writes it's faster than AtomicLong.

open as a page

Given AtomicInteger, atomic field updaters, and VarHandle, how do you decide which to use for low-level atomic field access?

level: middleimportance: should knowfreq 28%

basics

~20 s

Default to AtomicInteger/AtomicReference — they are simple and clear. Drop to an atomic field updater only on legacy code or to save memory across huge numbers of instances. For new low-level code that needs custom memory ordering or array-slot CAS, use VarHandle, the modern supported replacement for both updaters and sun.misc.Unsafe.

open as a page

How does AtomicStampedReference work, and how do you read and update it correctly?

level: seniorimportance: should knowfreq 44%

basics

~20 s

AtomicStampedReference holds a reference plus an int stamp. You read both together with get(int[]), and you compareAndSet the (reference, stamp) pair, bumping the stamp on each update so a value returning to its old self is still detected.

open as a page

Explain the contention problem with a shared AtomicLong and how striping in LongAdder addresses it at the hardware level.

level: seniorimportance: should knowfreq 40%

basics

~20 s

Many threads updating one AtomicLong keep failing their compare-and-swap and retrying because they all touch the same memory. That memory bounces between CPU caches. LongAdder gives each thread a different cell, so they touch different memory and stop fighting.

open as a page

How do CAS-based atomics compare to a synchronized counter, and when would you choose one over the other?

level: seniorimportance: should knowfreq 62%

basics

~20 s

Atomics use a lock-free CAS retry loop; synchronized takes a real lock. Atomics are usually faster and deadlock-free for single-variable updates, but synchronized is simpler and necessary when you must update several things together as one atomic block.

open as a page

What are the AtomicFieldUpdater classes, and why would you use one instead of wrapping every field in an AtomicInteger or AtomicReference?

level: seniorimportance: should knowfreq 30%

basics

~20 s

AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, and AtomicReferenceFieldUpdater let you do atomic updates (like compareAndSet) on a plain volatile field of a class. You use one shared updater per class instead of giving every object its own Atomic wrapper object, which saves memory when you have huge numbers of instances.

open as a page

What is a VarHandle, and why was it introduced as a replacement for sun.misc.Unsafe?

level: seniorimportance: should knowfreq 38%

basics

~20 s

A VarHandle (added in Java 9) is a typed reference to a variable — a field, array element, or off-heap location — that lets you read and write it with different levels of atomicity and memory ordering, including CAS. It is the safe, supported, standard replacement for the internal, unsupported sun.misc.Unsafe.

open as a page

Walk through how the ABA problem corrupts a lock-free Treiber stack, and how stamping prevents it.

level: principalimportance: should knowfreq 33%

basics

~20 s

In a lock-free stack, a thread reads head=A and plans to set head to A.next. If another thread pops A and B then pushes A back, head is A again with a different next. The first thread's CAS still succeeds, setting head to a stale node. A version stamp makes that CAS fail instead.

open as a page

What is AtomicMarkableReference, how does it differ from AtomicStampedReference, and when would you choose it?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

AtomicMarkableReference pairs a reference with a single boolean 'mark' instead of an int version. It's used to flag a node (e.g. logically deleted) atomically. It carries one bit, not a full history, so it doesn't fully defeat ABA the way a stamp does.

open as a page

What does LongAccumulator add over LongAdder, and when would you reach for it?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

LongAccumulator is the general version of LongAdder. You give it a function (like max or multiply) and a starting value, and it combines all the values you feed in using that function across multiple cells. LongAdder is just LongAccumulator with addition.

open as a page

How do you use a VarHandle to perform atomic operations on array elements, and why is that useful?

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

You create an array-element VarHandle with MethodHandles.arrayElementVarHandle(int[].class). Then operations like getVolatile or compareAndSet take the array plus an index, letting you atomically update one slot. This is useful for lock-free arrays — like a striped counter or a hash table's bucket array — without wrapping every slot in an Atomic object.

open as a page

You're profiling a service and a shared metric counter shows up as a hotspot under load. Walk through how you'd decide between AtomicLong, LongAdder, and other options.

level: principalimportance: nice to knowfreq 28%

basics

~20 s

First confirm it's really write contention, not something else. If many threads just increment a counter and you read it rarely, switch to LongAdder. If you need to read or compare-and-set a single exact value, keep AtomicLong. Or use a metrics library that already handles it.

open as a page

What is the ABA problem in CAS-based code, and how do you guard against it in Java?

level: principalimportance: nice to knowfreq 34%

basics

~20 s

ABA is when a value changes from A to B and back to A, so a CAS expecting A succeeds even though the data really changed underneath. In Java you guard against it with AtomicStampedReference, which adds a version number you also compare.

open as a page

Explain VarHandle's access modes — plain, opaque, acquire/release, and volatile — and how they differ in memory-ordering strength.

level: principalimportance: nice to knowfreq 22%

basics

~20 s

VarHandle lets you pick how strongly a read or write is ordered. Plain is a normal access with no guarantees; opaque keeps a single variable's accesses coherent and atomic; acquire/release pair up so a release write is seen by a later acquire read; volatile is the strongest, full ordering. You pick the weakest mode that is still correct, for speed.

open as a page