skip to content

The Java Memory Model & the volatile/final Keywords

The Java keywords that expose the memory model to application code: volatile, final, and synchronized, plus the double-checked-locking idiom built on them. The underlying happens-before and safe-publication theory lives with the JVM platform topics, and interviewers move between the two freely.

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

questions

9

You have a worker thread looping on a non-volatile boolean flag that another thread sets to false, but it never stops. Why, and how do you fix it?

level: juniorimportance: must knowfreq 70%

answer

  1. JIT hoists flag into a register
  2. no happens-before = stale read
  3. make the flag volatile
  4. single boolean write is already atomic
  5. blocked threads also need interrupt()

basics

~10 s

The worker may keep reading a cached copy of the flag and never see the update. Declare the flag volatile so every read sees the latest written value, and the loop will exit.

solid answer

~40 s

Without any synchronization the JIT compiler is allowed to assume the loop variable doesn't change inside the worker thread, so it can hoist the flag into a register and never re-read it from memory — the loop spins forever even after another thread sets it false. There is no happens-before relationship forcing the writing thread's update to become visible to the reader. The fix is to make the flag volatile: a volatile read always returns the most recent write, and the volatile read/write pair establishes the happens-before edge that makes the update visible. Alternatives that also work are guarding the flag with synchronized or using an AtomicBoolean. volatile is the lightest correct option here because we only need visibility, not compound atomicity — a single boolean assignment is already a single atomic write.

code

java · 13 lines
java
class Server {
    private volatile boolean stopped = false; // fix: volatile

    void serve() {
        while (!stopped) {   // re-reads the field each iteration
            handleRequest();
        }
    }

    void shutdown() {
        stopped = true;      // promptly visible to the worker
    }
}

go deeper

for a junior

Recognizes the symptom (infinite loop) and the one-word fix (make the flag volatile) and can state that the worker was reading a stale copy.

for a middle

Explains JIT hoisting and the missing happens-before edge, and why volatile (vs a lock) is the appropriate minimal fix.

for a senior

Adds the caveat that volatile only helps between iterations (blocked threads need interrupt) and contrasts volatile/AtomicBoolean/synchronized trade-offs.

for a principal

Discusses the JMM optimization latitude that legalizes hoisting, safepoint behavior, and designs a robust shutdown protocol combining a volatile flag with interruption.

## The scenario ```java class Server { private boolean stopped = false; // BUG: not volatile void serve() { while (!stopped) { // worker thread handleRequest(); } } void shutdown() { stopped = true; // control thread } } ``` You call `shutdown()` from another thread, but `serve()` keeps running. Nothing is broken in the *value* — `stopped` is genuinely set to `true`. The problem is **visibility**: the worker thread never *observes* the change. ## Why it happens The Java Memory Model gives the compiler/JIT and CPU wide latitude to optimize code *as if it were single-threaded*. Inside `serve()`, nothing the thread itself does modifies `stopped`, so the JIT is permitted to **hoist** the field into a CPU register once and reuse it, effectively rewriting the loop to: ```java if (!stopped) { while (true) handleRequest(); } ``` There is also no **happens-before** edge — the JMM's ordering relation — connecting the write in `shutdown()` to the read in `serve()`. With no such edge, the JMM does *not* require the reader to ever see the writer's update. So the loop can spin forever, or stop only by luck (e.g. after a safepoint or on a less aggressive JVM). ## The fix ```java private volatile boolean stopped = false; ``` A **volatile** read is guaranteed to return the latest value written to that field by any thread, and a volatile write *happens-before* a subsequent volatile read of the same field. That forbids the hoisting optimization (the field must be re-read each iteration) and creates the visibility edge. The loop now exits promptly after `shutdown()`. ## Why volatile is the right tool here (not a lock) We need exactly one thing: **visibility** of a single boolean. We do *not* need atomicity of a compound operation (a plain `stopped = true` is already a single atomic write), and we do not need mutual exclusion. So volatile is the lightest correct choice. Heavier alternatives that also work: - `synchronized` getter/setter around `stopped` — correct but adds locking overhead and contention. - `AtomicBoolean stopped` — correct; useful if you later need `compareAndSet`, otherwise overkill for a plain flag. ## Important caveats - volatile fixes *this* because the operations are single reads and single writes. If the flag logic became a compound update (read-then-decide-then-write across threads), volatile alone would no longer be enough. - Marking the field volatile is *not* a substitute for proper interruption when the thread is blocked (e.g. inside `sleep`/`wait`/IO) — a volatile flag is only checked between iterations, so a blocked thread also needs `Thread.interrupt()`.

  • Would synchronized or AtomicBoolean also fix this?
    Yes, both establish the necessary visibility/happens-before. synchronized adds lock overhead, and AtomicBoolean is useful if you also need compareAndSet. For a plain flag that only needs visibility, volatile is the lightest correct choice.
  • If the worker is blocked inside sleep() or a socket read, will the volatile flag stop it?
    No — a volatile flag is only checked between loop iterations. A thread blocked in a blocking call won't reach the check, so you also need Thread.interrupt() to unblock it and then re-check the flag.

It's like a worker checking a sticky note they photocopied once and keep glancing at the copy; the boss updates the original on the wall but the worker never looks back at the wall. volatile forces the worker to read the wall every time.

saying these in an interview costs you the question

  • Saying the value 'wasn't actually written' — it was; the issue is visibility, not the assignment.
  • Claiming you must use a lock/synchronized — volatile suffices for a single-flag visibility need.
  • Thinking volatile will break a thread out of a blocking sleep/IO call.

context

open as a page

What does the volatile keyword do in Java, and what problem does it solve?

level: middleimportance: must knowfreq 80%

basics

~20 s

Marking 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.

open as a page

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

level: middleimportance: must knowfreq 72%

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.

open as a page

Why is double-checked locking broken in Java without a volatile field? Explain the unsafe-publication / reordering failure concretely.

level: seniorimportance: must knowfreq 80%

basics

~20 s

Without volatile, the line instance = new T() can be reordered so the reference is set before the object's fields finish initializing. Another thread doing the lock-free first check can then see a non-null reference pointing at a half-built object and use it.

open as a page

What is lazy initialization for a singleton, and why might a naive synchronized getter become a performance concern under high contention?

level: juniorimportance: should knowfreq 55%

basics

~20 s

Lazy initialization means you only create an object the first time it is actually needed, not at startup. Putting synchronized on the whole getter makes it thread-safe but forces every caller to wait for the lock even after the object already exists.

open as a page

What is the initialization-on-demand holder idiom, and why is it often preferred over double-checked locking for lazy singletons?

level: middleimportance: should knowfreq 60%

basics

~20 s

You put the singleton in a private static nested 'holder' class that holds the instance in a static field. The JVM only loads and initializes that nested class the first time you touch it, and the JVM guarantees class initialization is thread-safe — so you get lazy, safe initialization with no locks or volatile.

open as a page

If you declare a volatile array (e.g. volatile int[] data), which accesses get volatile semantics — and how do you get per-element visibility?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Only the array reference is volatile — reassigning the whole array variable is visible. Writing an element (data[i] = x) is NOT volatile. For per-element visibility use AtomicIntegerArray or VarHandle.

open as a page

Explain the happens-before guarantee of a volatile write/read and how you can 'piggyback' the publication of other data on it.

level: seniorimportance: should knowfreq 55%

basics

~20 s

A volatile write happens-before any later read of that same field. So everything a thread wrote before writing the volatile flag becomes visible to another thread once it reads the flag — you can publish plain data by writing the volatile flag last.

open as a page

Given a singleton requirement, how would you decide between eager static init, the holder idiom, volatile double-checked locking, and an enum — and what trade-offs drive the choice?

level: principalimportance: nice to knowfreq 45%

basics

~20 s

If the object is cheap or always needed, use an eager static field (or an enum for the safest singleton). If you need lazy creation, prefer the holder idiom. Only reach for volatile double-checked locking when you need lazy init but the constructor requires runtime arguments the holder idiom can't pass.

open as a page