What does the volatile keyword do in Java, and what problem does it solve?
answer
- Visibility, not atomicity
- stop-flag loop bug
- happens-before / piggyback publish
- count++ still races
- acquire read / release write barriers
basics
~20 sMarking a field volatile guarantees that a thread reading it always sees the most recent value written by any other thread. It fixes the visibility problem where one thread's update can otherwise go unseen by another.
solid answer
~40 svolatile is a field modifier that guarantees visibility across threads: a read of a volatile field always returns the latest value written by any thread, so threads can't keep working off a stale cached copy. It solves the classic 'stop flag' bug, where a worker thread loops forever on a non-volatile boolean because the writing thread's update is never observed. Beyond visibility, volatile establishes a happens-before edge: everything a thread did before writing the volatile field is visible to another thread after it reads that same field. It also prevents the compiler/CPU from reordering memory operations across the access. Crucially, volatile gives no atomicity for compound operations: count++ on a volatile int is still a race because it is a separate read, increment, and write. For that you need synchronized or an AtomicInteger.
code
java · 17 linesclass Worker implements Runnable {
private volatile boolean running = true; // visibility guaranteed
public void run() {
while (running) { // always sees the latest value
doWork();
}
}
public void stop() {
running = false; // observed by the worker promptly
}
}
// But this is STILL a race even though the field is volatile:
volatile int count;
void inc() { count++; } // read+add+write is not atomic -> use AtomicIntegergo deeper
Knows volatile guarantees one thread sees another thread's latest write — fixes the stale stop-flag loop.
Distinguishes visibility from atomicity, knows count++ still races, and reaches for AtomicInteger/synchronized when compound updates are involved.
Explains the happens-before edge and piggyback publication, acquire/release barriers, the long/double atomicity bonus, and the volatile-array element pitfall.
Frames volatile within the Java Memory Model, reasons about when release/acquire is sufficient vs needing full mutual exclusion, and weighs it against atomics/locks for a given consistency requirement.
## The problem: visibility A running Java program may have multiple **threads** (independent paths of execution) on a multi-core CPU. For speed, each CPU core can cache values in registers or local cache, and the Java compiler/JIT may keep a field in a register and reorder instructions. The consequence: when thread A writes a field, thread B running on another core might keep reading an old (stale) copy and never see A's update. This is the **visibility problem**, and it is distinct from a value being computed wrong — the value is fine, it just isn't *propagated*. The canonical bug: ```java boolean running = true; // NOT volatile // thread 1 (worker): while (running) { doWork(); } // thread 2 later: running = false; // worker may NEVER see this and loops forever ``` The JIT is allowed to hoist `running` into a register because, within thread 1's view, nothing changes it, so the loop can spin forever. ## What volatile guarantees Declaring the field `volatile boolean running` provides three guarantees: 1. **Visibility.** A read of a volatile field always returns the *latest* value written by any thread. The value is never served from a stale thread-local cache. 2. **Ordering / happens-before.** A write to a volatile field *happens-before* every subsequent read of that same field. Concretely: everything a thread wrote (volatile *or* plain) *before* the volatile write becomes visible to a thread that later *reads* the volatile field. This is called **piggybacking**: you can publish ordinary data by writing one volatile flag last. 3. **No reordering across the access.** A volatile write has *release* semantics (no earlier memory operation can move after it); a volatile read has *acquire* semantics (no later memory operation can move before it). The compiler and CPU insert memory barriers (fences) to enforce this. The phrase "volatile means the variable is not cached" is a *simplification*. The real mechanism is the acquire/release **memory barriers**; on most hardware a volatile read is nearly free and a volatile write costs a fence. Thinking purely in "no caching" terms misleads on the ordering guarantees. ## What volatile does NOT give you: atomicity Visibility is not the same as **atomicity** (an operation completing as one indivisible step). A **compound** operation — read-modify-write — is still a race even on a volatile field: ```java volatile int count; count++; // really: read count, add 1, write count — three steps ``` Two threads can both read the same value, both add one, and both write it back, losing an increment. volatile fixes nothing here because each individual read and write is fresh, but the *sequence* isn't indivisible. For atomic counters use `AtomicInteger` (lock-free compare-and-swap) or a `synchronized` block. The one bonus: volatile *does* make reads/writes of 64-bit `long` and `double` atomic — without volatile, the JLS permits the JVM to split a 64-bit write into two 32-bit halves, so a reader could see a torn value; volatile (or any synchronization) forbids that tearing. ## Edge case: volatile arrays `volatile int[] arr` makes the **reference** volatile — reassigning `arr` is visible. It does *not* make element writes (`arr[3] = 5`) visible; the volatile applies to the array variable, not its slots. For per-element volatile semantics use `AtomicIntegerArray` or `VarHandle`. ## When to use it Use volatile for a single field that is written by one (or few) threads and read by many, where you need only visibility/ordering and not compound atomicity: status flags, a `done`/`shutdown` boolean, a cached reference published once, or the guard field of the double-checked-locking idiom. When you need to *also* coordinate multiple fields atomically, reach for a lock or an atomic/concurrent class instead.
- Why doesn't volatile make count++ thread-safe?count++ is a compound read-modify-write: read the value, add one, write it back. volatile makes each individual read and write fresh, but two threads can interleave the three steps and lose an update. Atomicity of the whole sequence needs AtomicInteger or synchronized.
- If I have a volatile reference to an object, are writes to that object's fields visible too?Only if they happened-before the volatile write that published the reference. If you mutate the object's fields after publishing it, those later mutations are not covered by the original volatile write and may be invisible. Publish a fully-built (ideally immutable) object.
A volatile field is like a shared whiteboard everyone reads from and writes to directly, instead of each person scribbling on a private notepad (their CPU cache). But a whiteboard doesn't stop two people from overwriting each other's 'count' at the same time — that needs a turn-taking lock.
saying these in an interview costs you the question
- Claiming volatile makes operations atomic (e.g. that volatile fixes count++).
- Saying volatile means 'a lock' or that it provides mutual exclusion.
- Believing volatile on an array makes element writes visible.
- Confusing visibility (seeing the latest value) with atomicity (indivisible operation).