Background:
When Java first appeared, there was no formal description of how memory and threads interact. As a result, the same programs could behave differently on different JVMs, leading to hard-to-trace bugs. In 2004, the Java Memory Model (JMM) was added to the Java specification to strictly define how threads see updates to variables.
The Problem:
Without a clear memory model, read and write operations can be rearranged by the compiler or processor, leading to unexpected behavior when accessing shared variables from different threads. This can cause race conditions, visibility issues, and hard-to-debug synchronization errors.
The Solution:
The JMM defines the rules: when changes made by one thread become visible to others. It establishes the concepts of happens-before, synchronizers (synchronized, volatile, final), internal locks, and limits instruction reordering in a multithreaded environment.
Example code:
class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int get() { return count; } }
Key Features:
Why does volatile not make operations atomic, but only ensures visibility?
Volatile guarantees only the visibility of changes between threads, but not the atomicity of modifications. For example, incrementing a volatile variable is not an atomic operation since the action consists of reading, modifying, and writing.
Example code:
volatile int count = 0; count++; // Not atomic!
Can a synchronized block ensure the visibility of changes outside of it?
Yes. After exiting a synchronized block, all changes made inside are visible to other threads that enter the block on the same object monitor.
When do changes to final fields become visible to other threads?
Final fields guarantee correct publication only if the object is fully constructed before passing references to it to other threads, for example, through immutable objects. Otherwise, visibility of an uninitialized state may occur.
Negative Case
The developer decided to increment a website access counter using a simple volatile increment from multiple threads.
Pros:
Cons:
Positive Case
Used AtomicInteger for the counter or synchronized methods.
Pros:
Cons: