How do the Java Memory Model's reordering rules work, and how do they affect concurrent code?
Both CPUs and the JIT compiler are free to reorder instructions for performance reasons -- a read might be hoisted to execute before a write that precedes it in the source code, or a write might be delayed. Within a single thread this reordering is invisible, because the processor always preserves the illusion of sequential execution for that thread's own instructions. Across threads, though, without any explicit synchronization, a second thread can observe the first thread's writes happening in a completely different order than they actually appear in the source code. The Java Memory Model formalizes exactly what visibility guarantees do exist between threads through the happens-before relationship -- without an established happens-before edge between two operations, no ordering guarantee exists at all. Declaring a field volatile establishes a memory barrier: every write that happened before a volatile write in program order is guaranteed to be flushed and visible, and every read that happens after a volatile read is guaranteed to see those flushed values. The synchronized keyword establishes equivalent memory barriers at the point a lock is acquired and at the point it is released. This is precisely why double-checked locking without a volatile field is broken: without the barrier a volatile write provides, another thread can see a non-null object reference before that object's own field writes performed inside its constructor have actually become visible.
Ready to master this question?
Generate a complete walkthrough — background, the full answer in plain language, a working code example explained line by line, a real-world scenario, common mistakes, and how this same question gets asked in different ways.
Sign in to generate a response