Java Memory Model Foundations: Visibility, Ordering, and Safe Publication
Build the mental model behind happens-before, volatile fields, monitors, and safely publishing objects between threads.
On this page
Key takeaways
- A thread-local observation is not automatically current just because another thread wrote the value.
- Happens-before relationships establish visibility and ordering, not a global chronological view.
- Final fields, volatile references, locks, and concurrent utilities are publication tools with different contracts.
- Prefer immutability and ownership boundaries before adding synchronization everywhere.
Why ordinary reads can surprise you
Java threads may run on different processor cores and use local caches. The compiler and processor may also reorder operations when the language rules allow it. If two threads access shared mutable state without a defined synchronization relationship, the reading thread is not guaranteed to observe the latest write in the order the author imagined. This is a data race, even if the code appears to work on a developer laptop.
The Java Memory Model describes which observations are permitted. Its central reasoning tool is happens-before: if action A happens-before action B, B must observe the effects required by A. Program order within one thread contributes happens-before edges. Releasing a monitor happens-before another thread subsequently acquires that same monitor. A write to a volatile field happens-before a later read of that field.
Visibility is not the same as atomicity
Declaring a reference volatile makes reads and writes of that reference visible and gives ordering guarantees around it. It does not make a compound update such as count++ atomic; that operation is a read, arithmetic, and write. Use a lock, an atomic class, or a design that avoids shared updates when several threads can modify the value.
Likewise, synchronizing one method does not protect a related field if other access paths ignore the same lock. A lock works as a protocol: every read and write that relies on the invariant must participate in it.
Safe publication patterns
An immutable object built completely before sharing is often the simplest pattern. Final fields receive special initialization guarantees when the constructor does not leak this. A volatile reference can publish a fully constructed replacement object. A concurrent collection safely coordinates access to its own mappings, but it does not automatically make mutable objects stored inside it safe.
Use thread confinement when possible: if one thread owns mutable state and communicates through messages or immutable snapshots, the problem is smaller. For shared state, document the synchronization policy close to the state so maintainers can see which lock or atomic operation owns each invariant.
A practical review checklist
For every shared mutable field, ask who writes it, who reads it, and which happens-before edge connects those actions. Check compound operations separately from simple reads and writes. Verify that object construction finishes before publication, and avoid calling overridable methods or starting threads from constructors that expose partially initialized state.
The memory model is not a reason to memorize every processor detail. It is a way to replace assumptions with explicit guarantees. Once visibility, atomicity, and ownership are treated as separate questions, concurrent Java code becomes much easier to review and test.