How to Answer: What Is Java Memory Model?
Short Interview Answer
The Java Memory Model (JMM) is a set of rules that defines how threads interact with shared data in memory. It tells us when changes made by one thread are visible to another thread, what ordering guarantees exist between operations, and which operations are atomic.
JMM is important because Java applications can run on multiple CPU cores, and the JVM, compiler, and CPU can optimize or reorder operations. JMM provides rules such as happens-before to make multithreaded behavior predictable.
The main concepts of JMM are visibility, ordering, atomicity, and happens-before. Java provides mechanisms such as volatile, synchronized, locks, and atomic classes to achieve the required guarantees.
Why Do We Need JMM?
Imagine an application has two threads. One thread changes a shared variable while another thread reads that variable. Without proper synchronization, the Java Memory Model does not guarantee that the reading thread will see the update made by the other thread.
class OrderService {
boolean orderCompleted = false;
void completeOrder() {
orderCompleted = true;
}
void sendNotification() {
while (!orderCompleted) {
// wait
}
System.out.println("Sending notification");
}
}- Thread 1 executes completeOrder()
- Thread 1 changes orderCompleted to true
- Thread 2 executes sendNotification()
- Thread 2 needs to see the updated value
- Without proper synchronization, visibility is not guaranteed
Visibility
Visibility means that when one thread changes a shared variable, another thread can correctly see that change.
class Worker {
boolean running = true;
void stop() {
running = false;
}
void work() {
while (running) {
// work
}
}
}Without proper synchronization, the thread executing work() is not guaranteed to observe the change made by the thread executing stop().
Fix Using volatile
volatile boolean running = true;With volatile, the JMM provides the required visibility guarantee for accesses to running.
Real-World Example: Background Worker
class PaymentProcessor {
private volatile boolean running = true;
void start() {
while (running) {
processPayments();
}
}
void shutdown() {
running = false;
}
}A background payment processor can continuously process payments. When the application shuts down, another thread can call shutdown(). Because running is volatile, the worker thread can correctly observe the change to false and exit the loop.
Ordering
Ordering means the JMM defines which operations are guaranteed to be observed before other operations when threads communicate through synchronization mechanisms.
int data = 0;
volatile boolean ready = false;
// Thread 1
data = 100;
ready = true;
// Thread 2
if (ready) {
System.out.println(data);
}The volatile write to ready establishes the necessary ordering relationship with a subsequent volatile read that observes that write. Therefore, when Thread 2 sees ready as true, it can safely observe the earlier write to data.
Easy Definition of Ordering
Ordering means the JMM defines which operations must be observed before other operations when threads communicate through synchronization mechanisms.
Atomicity
Atomicity means an operation happens as one indivisible operation from the perspective of other threads.
int count = 0;
// Two threads execute this
count++;count++ is not atomic. Conceptually, it involves reading count, adding one, and writing the result.
- Read count
- Add 1
- Write the result
If two threads read the same value and both increment it, one update can overwrite the other. For example, two threads can both read 10, both calculate 11, and both write 11. The final value becomes 11 instead of the expected 12.
Volatile Does Not Make count++ Atomic
volatile provides visibility and ordering guarantees, but it does not make compound operations such as count++ atomic.
volatile int count;
count++;Even though count is volatile, count++ still consists of multiple operations and is not automatically thread-safe.
How to Make an Increment Atomic
import java.util.concurrent.atomic.AtomicInteger;
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet();AtomicInteger provides atomic operations for integer values.
Using synchronized
class Counter {
private int count = 0;
synchronized void increment() {
count++;
}
synchronized int getCount() {
return count;
}
}Volatile
volatile is used when a variable is shared between threads and changes made by one thread need to be visible to other threads. It also provides ordering guarantees around volatile accesses.
class Worker {
private volatile boolean running = true;
void run() {
while (running) {
// work
}
}
void stop() {
running = false;
}
}- volatile provides visibility
- volatile provides ordering guarantees
- volatile does not provide general atomicity
- volatile is useful for simple shared state such as shutdown flags
Happens-Before
Happens-before is one of the most important JMM concepts. If operation A happens-before operation B, the effects of A are guaranteed to be visible to B and the required ordering relationship is established.
int data = 0;
volatile boolean ready = false;
// Thread 1
data = 100;
ready = true;
// Thread 2
if (ready) {
System.out.println(data);
}The volatile write ready = true happens-before a subsequent volatile read of ready that observes that write. Therefore, the earlier write data = 100 is guaranteed to be visible to Thread 2.
- data = 100
- ready = true
- volatile read of ready
- read data
synchronized and JMM
synchronized is not only used to prevent multiple threads from entering a critical section simultaneously. It also provides memory visibility and ordering guarantees.
class BankAccount {
private int balance = 1000;
synchronized void deposit(int amount) {
balance += amount;
}
synchronized int getBalance() {
return balance;
}
}When one thread releases a monitor and another thread subsequently acquires the same monitor, the synchronization establishes the required happens-before relationship.
Locks
Java provides explicit locks such as ReentrantLock. Locks provide synchronization and memory consistency guarantees while also offering additional control such as interruptible and timed lock acquisition.
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Counter {
private int count = 0;
private final Lock lock = new ReentrantLock();
void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}Thread.start() and JMM
Actions performed by a thread before calling Thread.start() happen-before actions in the started thread.
class Example {
int value = 100;
void printValue() {
System.out.println(value);
}
}
Example e = new Example();
e.value = 100;
Thread t = new Thread(e::printValue);
t.start();The actions performed before t.start() are ordered before the actions performed by the newly started thread.
Thread.join() and JMM
Actions performed by a worker thread happen-before another thread successfully returns from Thread.join(). This means the joining thread can safely observe the completed actions of the worker thread.
class Example {
int result;
}
Example example = new Example();
Thread worker = new Thread(() -> {
example.result = 100;
});
worker.start();
worker.join();
System.out.println(example.result);Safe Publication
Safe publication means making an object available to other threads in a way that guarantees the other threads see the correctly initialized state of that object.
- volatile fields
- synchronized blocks or methods
- Lock implementations
- Static initialization
- Concurrent collections
- Thread-safe queues
class UserService {
private volatile User user;
void publish(User user) {
this.user = user;
}
User getUser() {
return user;
}
}Final Fields
The JMM provides special initialization guarantees for final fields when an object is properly constructed. This is one reason immutable objects are useful in concurrent applications.
class User {
private final String name;
private final int age;
User(String name, int age) {
this.name = name;
this.age = age;
}
}Once constructed correctly, the state represented by these final fields cannot be changed.
JMM vs JVM Memory Areas
JMM and JVM memory areas are different concepts. The JMM focuses on how threads interact with shared data, including visibility, ordering, atomicity, and happens-before. JVM memory areas describe runtime memory structures.
JMM
- Threads
- Shared variables
- Visibility
- Ordering
- Atomicity
- Happens-before
JVM Memory Areas
- Heap
- Stack
- Metaspace
- PC Register
- Native Method Stack
If an interviewer asks about the Java Memory Model, focus on multithreading and shared-memory behavior rather than explaining Heap, Stack, and Metaspace.
Real-World Example: E-Commerce Order Processor
class OrderProcessor {
private volatile boolean running = true;
void process() {
while (running) {
processNextOrder();
}
}
void shutdown() {
running = false;
}
private void processNextOrder() {
System.out.println("Processing order...");
}
}The application can start the worker on one thread and call shutdown() from another thread. Because running is volatile, the worker thread can correctly observe the shutdown request and exit its loop.
OrderProcessor processor = new OrderProcessor();
Thread worker = new Thread(processor::process);
worker.start();
// Later during application shutdown
processor.shutdown();Real-World Example: API Request Counter
Imagine an API server where many threads process requests and each request increments a shared counter. Using requestCount++ without synchronization can cause lost updates.
import java.util.concurrent.atomic.AtomicInteger;
class RequestCounter {
private final AtomicInteger requestCount = new AtomicInteger();
void requestReceived() {
requestCount.incrementAndGet();
}
int getCount() {
return requestCount.get();
}
}AtomicInteger performs the increment atomically, making it appropriate for this type of shared counter.
The Four Words You Must Remember
Visibility
Can another thread see my change?
Example: volatile
Ordering
What order are operations guaranteed to be observed in?
Examples: volatile, synchronized, and locks.
Atomicity
Can an operation be safely performed as one indivisible operation?
Examples: AtomicInteger, synchronized, and locks.
Happens-Before
If A happens-before B, the relevant effects of A are guaranteed to be visible to B and the required ordering is established.
- Volatile write → subsequent volatile read
- Unlock → subsequent lock on the same monitor
- Actions before Thread.start() → actions in the started thread
- Actions in a thread → successful Thread.join() return
Final Interview Answer
Java Memory Model, or JMM, defines the rules for how multiple threads interact with shared memory. It mainly deals with visibility, ordering, atomicity, and happens-before relationships. For example, if one thread changes a shared variable, JMM defines when another thread is guaranteed to see that change. volatile provides visibility and ordering guarantees, while synchronized and locks provide mutual exclusion along with memory visibility and ordering. JMM also defines happens-before relationships. For example, a volatile write happens-before a subsequent read of that volatile variable, unlocking a monitor happens-before another thread subsequently locks that monitor, and actions before Thread.start() happen-before actions in the started thread. Atomicity is another important concept. count++ is not atomic because it involves read, modify, and write operations, so we can use AtomicInteger, synchronization, or locks when necessary. In simple terms, JMM gives Java developers predictable rules for communication and synchronization between threads.
Interview Questions
- What is Java Memory Model?
- Why do we need JMM?
- What is visibility in JMM?
- What is ordering in JMM?
- What is atomicity?
- What is volatile?
- Does volatile make count++ atomic?
- What is happens-before?
- How does synchronized relate to JMM?
- What happens if volatile is removed?
- What is safe publication?
- What is the difference between JMM and JVM memory areas?
- What happens-before Thread.start()?
- What happens-before Thread.join()?
- When should we use volatile instead of synchronized?
- Why is AtomicInteger used in multithreaded applications?