skip to content

When would you choose volatile over synchronized or an Atomic class, and when is volatile insufficient?

level: middleimportance: must knowfreq 72%

answer

  1. visibility-only -> volatile
  2. atomic one-field update -> Atomic/CAS
  3. multi-field invariant -> synchronized/Lock
  4. count++ needs Atomic, not volatile
  5. cost: volatile < atomic < synchronized

basics

~20 s

Use volatile when you only need a single field's latest value to be visible across threads and you don't combine reads and writes. Use synchronized or an Atomic class when you need atomic compound updates (like count++) or to coordinate several fields together.

solid answer

~50 s

volatile gives visibility and ordering for a single field but no atomicity for compound operations and no mutual exclusion. Choose volatile when one or few threads write a single independent field that many threads read, and a single read or single write is all you need — status flags, a published reference, the double-checked-locking guard. Choose an Atomic class (AtomicInteger, AtomicReference) when you need a lock-free atomic read-modify-write on one variable, such as a concurrent counter via incrementAndGet or a CAS-based update loop. Choose synchronized (or a Lock) when you must update multiple fields together as an invariant, when an operation spans several steps that must be exclusive, or when you need wait/notify coordination. The decision ladder: pure visibility of one field -> volatile; atomic update of one field -> Atomic; multi-field invariant or critical section -> synchronized/Lock. volatile is insufficient the moment correctness depends on a read and a later write being indivisible.

code

java · 12 lines
java
// volatile: visibility of a single flag
private volatile boolean shutdown;

// Atomic: lock-free atomic counter (volatile would race on ++ )
private final AtomicInteger hits = new AtomicInteger();
void record() { hits.incrementAndGet(); }

// synchronized: keep two fields consistent together
private int low, high; // invariant low <= high
synchronized void setRange(int l, int h) {
    if (l <= h) { low = l; high = h; } // both updated atomically
}

go deeper

for a junior

Knows volatile is for visibility and that counters need something stronger like synchronized or AtomicInteger.

for a middle

Can apply the decision ladder: volatile for single-field visibility, Atomic for one-field atomic updates, synchronized for multi-field invariants, and names the counter/check-then-act traps.

for a senior

Reasons about CAS, contention costs (volatile < atomic < synchronized), LongAdder under heavy write load, and the visibility guarantees each tool shares.

for a principal

Selects mechanisms against a stated consistency/throughput requirement, designs lock-free vs lock-based data structures, and justifies trade-offs at scale.

## Three tools, three jobs Java offers escalating tools for shared mutable state. Picking the wrong one either under-protects (a race) or over-protects (needless contention). ### volatile — visibility + ordering, one field A **volatile** field guarantees: reads see the latest write (visibility), and a happens-before edge orders the write before subsequent reads (ordering). It provides **no atomicity** for read-modify-write and **no mutual exclusion**. It is the cheapest tool: a volatile read is nearly free; a volatile write costs a memory barrier. *Use it when:* a single independent field needs to be seen across threads and each operation is a single read or single write. Examples: a `volatile boolean shutdown` flag, a `volatile long lastUpdatedTimestamp`, a one-time-published `volatile Config config`, the `volatile` instance in double-checked locking. ### Atomic classes — lock-free atomic updates, one variable `AtomicInteger`, `AtomicLong`, `AtomicReference`, etc. wrap a single value and offer **atomic compound operations** built on **compare-and-swap (CAS)** — a hardware primitive that updates a value only if it still holds an expected prior value, retrying in a loop otherwise. So `incrementAndGet()`, `getAndAdd()`, `updateAndGet()`, and `compareAndSet()` are atomic without locking. *Use it when:* you need an atomic read-modify-write on **one** variable — a concurrent counter, a lock-free stack head, an atomically-swapped reference. Under heavy write contention, prefer `LongAdder` over `AtomicLong`. ### synchronized / Lock — mutual exclusion, multiple fields or steps `synchronized` (or `java.util.concurrent.locks.Lock`) provides **mutual exclusion**: only one thread holds the monitor at a time, and it carries the same visibility/happens-before guarantees as volatile on entry/exit. It is the only tool that lets you keep **several fields consistent** or perform a **multi-step** operation atomically, and it enables `wait`/`notify` coordination. *Use it when:* an invariant spans multiple fields (e.g. `from -= amount; to += amount` in a transfer), a check-then-act must be exclusive, or you need condition waiting. ## The decision ladder 1. Do I only need other threads to *see* the latest value of **one** field, with single reads/writes? -> **volatile**. 2. Do I need an *atomic update* (read-modify-write) of **one** field? -> **Atomic** class. 3. Do I need to update **multiple** fields together, an exclusive multi-step section, or condition waiting? -> **synchronized / Lock**. ## Where volatile is insufficient (classic traps) - **Counters:** `volatile int count; count++;` loses updates — the increment isn't atomic. -> `AtomicInteger`. - **Check-then-act:** `if (instance == null) instance = new X();` across threads is a race even with volatile reads. -> double-checked locking *with* volatile *and* a lock, or the holder idiom. - **Coupled fields:** keeping `low <= high` consistent across two volatile fields fails, because a reader can see a new `low` with an old `high`. -> a lock around both. ## Cost intuition volatile < Atomic (CAS, may spin under contention) < synchronized (monitor, may block/park). Reach for the lightest tool that is *correct* — but never trade correctness for cheapness.

  • Is volatile cheaper than synchronized, and does that mean you should always prefer it?
    It is cheaper (a barrier vs a monitor that can block), but only prefer it when it is correct. If you need atomic compound updates or multi-field invariants, volatile is wrong regardless of cost — correctness first, then pick the lightest correct tool.
  • Why use AtomicInteger instead of synchronized for a counter?
    AtomicInteger uses lock-free CAS, so under moderate contention it avoids blocking and context switches that a synchronized block incurs, giving better throughput. Both are correct; the atomic is usually faster for a single-variable counter.

volatile is a public scoreboard everyone can read live; an Atomic is a scoreboard with a built-in 'only change it if it still says X' rule so two people can't clobber each other; synchronized is handing one person the only key to the whole room so they can rearrange several things consistently.

saying these in an interview costs you the question

  • Using volatile for a counter or any read-modify-write.
  • Using volatile to keep two related fields consistent (a reader can see one updated and one stale).
  • Claiming synchronized and volatile are interchangeable — only synchronized gives mutual exclusion.
  • Choosing the heaviest tool everywhere out of fear, serializing code that only needed visibility.

context