Explain Java memory model.
The Java Memory Model (JMM) defines how threads interact with memory and how changes made by one thread become visible to others. It specifies guarantees about when writes made by one thread are visible to reads made by another thread, ensuring consistent behavior across different hardware architectures and Java Virtual Machine (JVM) implementations. The JMM is crucial for understanding concurrency in Java, addressing issues of visibility, ordering, and atomicity.
Core Concepts of JMM
The JMM is based on the concepts of main memory and working memory (also known as local memory or cache) for each thread. When a thread accesses a variable, it makes a copy of it in its working memory. All operations (read, write) are performed on this working copy. The JMM specifies when changes made to a variable in one thread's working memory are flushed back to main memory and when another thread's working memory copy is invalidated or updated from main memory.
Happens-Before Relationship
The happens-before relationship is the cornerstone of the JMM. It is a partial ordering of all actions within a program that guarantees memory visibility. If action A happens-before action B, then the effects of A are visible to B, and A must precede B. Without a happens-before relationship, the JVM is free to reorder operations for performance, which can lead to unexpected results in concurrent programs.
- Program Order Rule: Each action in a thread happens-before every subsequent action in that same thread.
- Monitor Lock Rule: An unlock on a monitor happens-before every subsequent lock on that same monitor.
- Volatile Variable Rule: A write to a
volatilevariable happens-before every subsequent read of that samevolatilevariable. - Thread Start Rule: A
Thread.start()on a thread happens-before any action in the started thread. - Thread Termination Rule: All actions in a thread happen-before any other thread detects that thread has terminated (e.g., via
Thread.join()orisAlive()returningfalse). - Interruption Rule: A call to
Thread.interrupt()happens-before the interrupted thread detects the interrupt (by throwingInterruptedExceptionor callingisInterrupted()). - Finalizer Rule: The constructor of an object happens-before the start of its finalizer.
- Transitivity: If A happens-before B, and B happens-before C, then A happens-before C.
Key JMM Constructs
Java provides several language constructs that leverage the JMM to establish happens-before relationships and ensure proper memory synchronization.
Volatile Keyword
The volatile keyword ensures that reads and writes to a variable are directly from and to main memory, preventing a thread from caching the value locally. It guarantees visibility of changes across threads but does not provide atomicity for compound operations. The Volatile Variable Rule establishes a happens-before relationship.
class SharedData {
volatile boolean flag = false;
public void setFlag() {
flag = true; // Write to volatile
}
public void checkFlag() {
if (flag) { // Read from volatile
System.out.println("Flag is true.");
}
}
}
Synchronized Keyword
The synchronized keyword provides mutual exclusion and memory visibility. When a thread enters a synchronized block or method, it acquires a monitor lock. Upon exiting, it releases the lock. The Monitor Lock Rule ensures that an unlock on a monitor happens-before any subsequent lock on that same monitor, guaranteeing that all changes made inside the synchronized block are visible to any other thread subsequently acquiring the same lock.
class Counter {
private int count = 0;
public synchronized void increment() {
count++; // Atomic and visible operation
}
public synchronized int getCount() {
return count;
}
}
Final Fields
The JMM guarantees that once an object's constructor finishes, all its final fields are visible to other threads without explicit synchronization. This means that after a properly constructed object is published, all threads are guaranteed to see the correct values of its final fields immediately.
| Construct | Memory Visibility | Atomicity |
|---|---|---|
| volatile | Guaranteed | No (for compound ops) |
| synchronized | Guaranteed | Guaranteed (for block) |
| final fields | Guaranteed (after construction) | N/A |
Importance and Common Pitfalls
Understanding the JMM is vital for writing correct and performant concurrent applications. Misunderstanding it can lead to subtle bugs such as stale data reads, lost updates, or deadlock. Always ensure proper synchronization mechanisms are in place when multiple threads access shared mutable state.
- Data Races: When multiple threads access the same shared mutable data without proper synchronization, and at least one access is a write.
- Stale Data: A thread reads an outdated value of a variable because another thread's write is not yet visible.
- Reordering: The JVM and hardware can reorder instructions for performance, which can break program logic if not constrained by happens-before rules.