Volatile
Here is the updated interview preparation guide strictly focused on Java. It covers how volatile interacts with the Java Memory Model (JMM), thread visibility, instruction reordering, and atomic vs non-atomic operations from basic to advanced levels.
Basic Level¶
Q1: What is the volatile keyword in Java?¶
Answer: In Java, volatile is a field modifier used to mark a variable as "stored in main memory."
It tells the Java Virtual Machine (JVM) and the CPU never to cache the variable's value in thread-local CPU registers or L1/L2 caches. Every read of a volatile variable is fetched directly from main RAM, and every write is flushed immediately back to main RAM.
Q2: What core concurrency problem does volatile solve?¶
Answer: It solves the Visibility Problem.
By default, in multi-core processors, each thread running on a separate core keeps a local copy of variables in its CPU cache for performance. If Thread A updates a shared variable, Thread B running on another core might not see that update immediately (or ever) because it reads from its own cache.
Marking a variable volatile guarantees cross-thread visibility: any thread that reads the variable will always see the most recent value written by any other thread.
// Common Use-Case: Shutdown Flag
public class ServerTask implements Runnable {
// Volatile guarantees loop termination when flag changes in another thread
private volatile boolean keepRunning = true;
public void stopServer() {
this.keepRunning = false; // Immediately visible to worker thread
}
@Override
public void run() {
while (keepRunning) {
// Processing incoming requests...
}
}
}
Intermediate Level¶
Q3: Does volatile make operations thread-safe or atomic?¶
Answer: No. volatile guarantees Visibility and Ordering, but NOT Atomicity.
A classic interview trick question asks if volatile int count = 0; makes count++ thread-safe. It does not.
The operation count++ is not a single atomic instruction. It involves three distinct steps:
- Read
countfrom main memory. - Increment the value in the CPU register.
- Write the updated value back to main memory.
If two threads execute count++ concurrently, both can read the same starting value before either completes the write, resulting in a lost update (a Race Condition).
Q4: When should you use volatile vs synchronized?¶
Answer:
| Feature | volatile | synchronized |
|---|---|---|
| Mechanics | Non-blocking variable modifier. | Blocking mutual exclusion lock. |
| Guarantees | Visibility & Ordering only. | Visibility, Ordering, and Atomicity. |
| Performance | High (no thread context switching or lock contention). | Lower (threads block/wait for lock acquisition). |
| Best Used For | Status flags, state indicators, single reference updates. | Compound operations (count++), invariant checks, multi-field updates. |
Q5: When should you AVOID using volatile in Java?¶
Answer: You should avoid volatile in the following scenarios:
- Compound operations / Invariant updates: Any time a write depends on the current value (e.g.,
count++,total += value, or checkingif (x == 5) x = 10;). UseAtomicIntegerorsynchronizedinstead. - Dependent variables: When multiple variables must be updated together atomically (e.g., updating both
xandycoordinates). - Immutability: If a field is declared
final, it is already thread-safe after construction and does not needvolatile. - Local variables:
volatilecannot be applied to method-local variables; local variables reside on the thread stack and are never shared across threads.
Advanced Level¶
Q6: What is the "Happens-Before" relationship in JMM, and how does volatile affect it?¶
Answer: The Java Memory Model (JMM) defines execution constraints via the Happens-Before principle. If operation A happens-before operation B, the memory writes done by A are guaranteed to be visible to B.
For volatile variables:
- Volatile Write Rule: A write to a
volatilevariable happens-before every subsequent read of that samevolatilevariable. - Piggybacking (Transitivity) Rule: When Thread A writes to a
volatilevariable, all non-volatile variables written by Thread A before that point are also flushed to main memory and made visible to Thread B when it reads that samevolatilevariable.
public class VisibilityExample {
private int data = 0; // Non-volatile
private volatile boolean ready = false; // Volatile flag
public void writerThread() {
data = 42; // Step 1
ready = true; // Step 2 (Volatile write flushes 'data' too!)
}
public void readerThread() {
if (ready) { // Step 3 (Volatile read)
System.out.println(data); // Step 4 (Guaranteed to print 42, not 0)
}
}
}
Q7: How does volatile prevent Instruction Reordering under the hood?¶
Answer: Modern JIT compilers and CPU architectures reorder bytecode/machine instructions to maximize CPU instruction pipeline usage. However, reordering across thread boundaries can break logic.
To prevent illegal reordering, the JVM inserts Memory Barriers (Fences) into generated assembly code around volatile accesses:
- StoreStore Barrier: Inserted before a volatile write. Ensures all preceding normal writes are completed before the volatile write occurs.
- StoreLoad Barrier: Inserted after a volatile write. Ensures the volatile write is visible to all processors before any subsequent read/write.
- LoadLoad / LoadStore Barrier: Inserted after a volatile read. Ensures subsequent reads/writes cannot be reordered before this volatile read.
Q8: Why is volatile strictly required in the Double-Checked Locking Singleton pattern?¶
Answer: Without volatile, Double-Checked Locking suffers from object creation reordering.
public class Singleton {
// MUST be volatile
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // First Check
synchronized (Singleton.class) {
if (instance == null) { // Second Check
instance = new Singleton(); // Problematic without volatile!
}
}
}
return instance;
}
}
Why it fails without volatile: Creating an object (instance = new Singleton()) executes in 3 steps:
- Allocate memory.
- Run constructor (initialize instance fields).
- Assign pointer
instanceto the allocated memory location.
Without volatile, the compiler/CPU can reorder execution to 1 $\rightarrow$ 3 $\rightarrow$ 2.
If Thread A executes step 1 and step 3, instance is no longer null, but the constructor (step 2) hasn't finished. If Thread B calls getInstance(), it passes the first check, skips lock acquisition, and returns a partially constructed object, leading to unpredictable crashes or corrupted data in Thread B.
volatile forces step 2 to complete before step 3 becomes visible to other threads.
Quick Interview Cheat Sheet¶
- Primary Purpose: Thread visibility & instruction ordering prevention.
- Memory Mechanism: Bypasses thread-local CPU caches; uses Memory Barriers.
- Guarantees: Visibility + Ordering.
- Does NOT Guarantee: Atomicity (not safe for
count++). - Key Alternatives:
java.util.concurrent.atomicclasses (e.g.,AtomicInteger),ReentrantLock,synchronized.