The Java Memory Model (JMM) defines how threads interact through memory — specifically, how changes to variables made by one thread become visible to others.
It’s part of the Java Language Specification and is enforced by the Java Virtual Machine (JVM) to ensure consistent behavior across all platforms.
💡 Why Do We Need JMM?
Java programs run on many architectures. Some CPUs and compilers reorder instructions for performance. Without a well-defined memory model, this can lead to unpredictable behavior in multithreaded code.
The JMM provides a framework to handle:
- Visibility (when one thread sees updates from another)
- Atomicity (whether an operation is indivisible)
- Ordering (whether operations occur in the order we expect)
🧩 Key Concepts in JMM
1. Main Memory and Working Memory
- Main Memory: Shared among all threads.
- Working Memory: Each thread has its own local cache of variables (similar to CPU cache).
- Threads read from main memory into local memory, work on it, and write back.
2. Happens-Before Relationship
The happens-before rule determines visibility:
- If A happens-before B, then A’s effects are visible to B.
Examples:
- A call to
Thread.start()
happens-before actions in the started thread. Thread.join()
happens-after all actions in the joined thread.synchronized
blocks andvolatile
variables create happens-before relationships.
3. Volatile Keyword
- Declaring a variable
volatile
ensures:- Visibility: Changes made by one thread are visible to others immediately.
- No caching: It tells the JVM not to cache the variable in a thread’s working memory.
- No atomicity for compound actions (like
count++
).
4. Synchronized Blocks
- Guarantees:
- Mutual exclusion (only one thread at a time can access the block).
- Visibility: Entering a
synchronized
block flushes the cache; changes are written back to main memory when exiting.
5. Final Fields and Immutability
- The JMM guarantees special treatment for
final
fields. They become visible after the constructor finishes. - This helps build immutable objects safely in multi-threaded code.
🔄 Instruction Reordering
- Compilers and processors may reorder instructions for performance.
- The JMM defines rules so that reordering doesn’t break thread-safe programs (as long as they follow the JMM rules).
⚠️ Common Pitfalls Without JMM Awareness
- Reading stale data (thread doesn’t see the latest value)
- Out-of-order execution can cause logical errors
- Race conditions and data corruption
🧪 Example
class SharedObject {
boolean flag = false;
int value = 0;
void writer() {
value = 42; // 1
flag = true; // 2
}
void reader() {
if (flag) { // 3
System.out.println(value); // 4
}
}
}
Without synchronization or volatile
, the reader thread may see flag = true
but value = 0
, due to instruction reordering or caching.
✅ To fix this: make flag
volatile.
🚦 Summary
Concern | Tool/Keyword | Ensures |
---|---|---|
Visibility | volatile | Other threads see updates immediately |
Ordering | synchronized | Guarantees happens-before |
Atomicity | AtomicInteger , synchronized | Prevents interleaving |
Reordering | happens-before rules | Enforced ordering of operations |