The Java Memory Model defines how threads observe shared state. Without it, code that “works on my machine” can fail under production scheduling, CPU reorderings, and JIT optimizations.
Why This Topic Matters
In backend systems, data races usually appear as rare, non-reproducible defects:
- stale reads during request handling
- partially published objects
- broken double-checked initialization
- reordering-sensitive flags
JMM gives the formal rules to prevent these classes of failures.
Core Concepts You Must Internalize
- Atomicity: a single read/write operation may or may not be atomic depending on type and operation.
- Visibility: one thread’s writes are not guaranteed visible to another without a happens-before edge.
- Ordering: compiler/CPU may reorder instructions unless constrained by synchronization semantics.
- Happens-before: the contract that guarantees visibility + ordering between two actions.
Happens-Before Edges That Matter in Real Code
- unlock happens-before subsequent lock of same monitor
- volatile write happens-before subsequent volatile read of same variable
- thread start and join establish visibility boundaries
- final-field publication has special guarantees when object is safely constructed
Java Implementation Example
final class ConfigHolder {
private static volatile ConfigHolder INSTANCE;
private final Map<String, String> values;
private ConfigHolder(Map<String, String> values) {
this.values = Map.copyOf(values); // immutable + final field
}
static ConfigHolder getInstance() {
ConfigHolder local = INSTANCE;
if (local == null) {
synchronized (ConfigHolder.class) {
local = INSTANCE;
if (local == null) {
local = new ConfigHolder(loadConfig());
INSTANCE = local; // volatile write publishes fully initialized object
}
}
}
return local;
}
String get(String key) {
return values.get(key);
}
private static Map<String, String> loadConfig() {
return Map.of("region", "ap-south-1");
}
}
Common Production Mistakes
- Using non-volatile stop flags in long-running loops.
- Publishing mutable objects without synchronization.
- Assuming
HashMapreads are safe during concurrent writes. - Treating successful tests as proof against race conditions.
Verification Strategy
- Add stress tests that run concurrency paths in loops.
- Use thread sanitizer or race-detection tooling where possible.
- Reduce shared mutable state and prefer immutable snapshots.
- Encode synchronization assumptions in code comments near shared fields.
Key Takeaways
- JMM is a correctness contract, not optional theory.
- Correct publication and happens-before edges are mandatory in concurrent Java code.
- Most reliability wins come from reducing shared mutation and making visibility explicit.
Publication Pattern for Shared State
A practical way to reduce JMM bugs is to avoid sharing mutable objects directly. Publish immutable snapshots and replace references atomically.
final class RoutingTable {
private final AtomicReference<Map<String, Route>> table =
new AtomicReference<>(Map.of());
void reload(Map<String, Route> latest) {
table.set(Map.copyOf(latest));
}
Route find(String key) {
return table.get().get(key);
}
}
This pattern removes lock contention on reads and gives a clear happens-before edge via atomic publication.
Real Incident Pattern: Stale Config Under Load
A common incident shape is stale configuration during rolling reload. One thread updates config while request threads read mixed old and new state.
Use immutable config snapshots and versioned swaps:
- parse config into immutable object graph
- validate invariants before publication
- atomically swap reference
- emit metric with config version and timestamp
This model gives deterministic visibility and avoids partial publication bugs.
Dry Run: Non-Volatile Stop Flag Bug
class Worker {
boolean running = true; // bug: no visibility guarantee
void loop() {
while (running) {
// work
}
}
}
Thread A sets running = false, but Thread B may keep reading cached true and never stop.
Fix:
volatile boolean running = true;
Now write in Thread A happens-before subsequent read in Thread B.
Code Review Checklist (JMM)
When reviewing concurrent code, ask:
- where is shared mutable state?
- what exact happens-before edge protects it?
- can readers observe partially constructed objects?
- are stop/cancel flags volatile or lock-protected?
These checks catch most high-severity JMM issues early.
Categories
Tags