Java - Java Memory Model (JMM) – Detailed Explanation
The Java Memory Model (JMM) is a formal specification in Java that defines how threads interact with memory. It describes how variables are stored in memory, how threads access them, and how changes made by one thread become visible to others. Understanding JMM is essential for writing correct and efficient multithreaded programs.
1. Why the Java Memory Model Exists
In a single-threaded program, execution order is predictable. However, in multithreaded environments, multiple threads may read and write shared variables simultaneously. Modern systems introduce complexity due to:
-
CPU caches
-
Compiler optimizations
-
Instruction reordering
-
Multiple cores executing in parallel
Without a proper memory model, programs could behave unpredictably. JMM ensures consistency and defines rules that JVM and hardware must follow.
2. Main Memory vs Thread Local Memory
JMM divides memory into two conceptual parts:
Main Memory
-
Shared among all threads
-
Stores actual values of variables
Thread Local Memory (Working Memory)
-
Each thread has its own copy of variables
-
Threads read/write from their local copy for performance
A thread does not always read the latest value from main memory. It may use a cached value from its working memory, which can lead to visibility issues.
3. Core Problems in Multithreading
a) Visibility Problem
When one thread updates a variable, another thread may not see the updated value immediately.
Example:
-
Thread A sets
flag = true -
Thread B may still see
flag = false
This happens because Thread B may be using a cached value.
b) Atomicity Problem
Operations that appear single-step may actually be multiple steps.
Example:
count++
This is actually:
-
Read value
-
Increment
-
Write back
If two threads execute this simultaneously, results may be incorrect.
c) Ordering Problem (Reordering)
For performance, the compiler and CPU may reorder instructions.
Example:
int a = 1;
int b = 2;
Execution order may change internally, as long as single-threaded behavior is preserved. But in multithreading, this can cause unexpected results.
4. Happens-Before Relationship
JMM introduces the concept of happens-before, which guarantees visibility and ordering.
If one action happens-before another, then:
-
Changes made by the first are visible to the second
-
Execution order is preserved
Key happens-before rules:
-
Program order rule: Statements execute in order within a thread
-
Monitor lock rule: Unlock happens-before subsequent lock
-
Volatile rule: Write to volatile variable happens-before read
-
Thread start rule: Thread start happens-before its execution
-
Thread join rule: Thread completion happens-before join returns
5. Role of volatile
The volatile keyword ensures:
-
Visibility: Changes are immediately visible to all threads
-
No caching: Threads read from main memory
-
Ordering: Prevents certain reordering
Example:
volatile boolean flag = false;
If one thread sets flag = true, other threads will immediately see the change.
Limitations:
-
Does not guarantee atomicity
-
Not suitable for compound operations like increment
6. Role of synchronized
The synchronized keyword provides:
-
Mutual exclusion (only one thread executes a block at a time)
-
Visibility (changes are flushed to main memory)
-
Ordering guarantees
Example:
synchronized(obj) {
count++;
}
When a thread exits a synchronized block:
-
All changes are written to main memory
When another thread enters:
-
It reads fresh values from main memory
7. Instruction Reordering and Its Control
JMM allows reordering for performance but restricts it when:
-
Dependencies exist
-
Synchronization constructs are used
Using volatile and synchronized prevents harmful reordering.
8. As-if-Serial Semantics
Even though instructions may be reordered internally, JMM ensures that:
-
The outcome of a single-threaded program remains unchanged
This is called as-if-serial behavior.
9. Final Fields and Safe Publication
Fields declared as final have special guarantees:
-
Once initialized in a constructor, they are safely visible to other threads
-
No reordering allowed during initialization
Safe publication means making an object visible to other threads in a correct way, using:
-
Volatile variables
-
Synchronized blocks
-
Immutable objects
10. Practical Implications
Understanding JMM helps developers:
-
Avoid subtle concurrency bugs
-
Write thread-safe code
-
Choose between
volatile,synchronized, and other concurrency utilities -
Optimize performance without breaking correctness
Conclusion
The Java Memory Model is the backbone of Java concurrency. It defines how threads interact with memory, ensuring predictable behavior even in complex multithreaded systems. By understanding visibility, atomicity, ordering, and happens-before relationships, developers can build reliable and efficient concurrent applications.