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?
answer
- JIT hoists flag into a register
- no happens-before = stale read
- make the flag volatile
- single boolean write is already atomic
- blocked threads also need interrupt()
basics
~10 sThe 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 sWithout 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 linesclass 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
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.
Explains JIT hoisting and the missing happens-before edge, and why volatile (vs a lock) is the appropriate minimal fix.
Adds the caveat that volatile only helps between iterations (blocked threads need interrupt) and contrasts volatile/AtomicBoolean/synchronized trade-offs.
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.