Why interviewers love this one: it looks simple, but it separates “I memorized a keyword” from “I understand the Java Memory Model.”
Question: What does the volatile keyword do in Java?
Answer: volatile tells Java that a field is shared across threads and must be read and written in a way that other threads can see the latest value. It gives you visibility and ordering guarantees, but it does not make operations like count++ atomic. In simple terms: everyone sees the newest note, but two people can still overwrite each other if they write at the same time.
Interview-Ready Answer: In Java, I use volatile when I need a shared field to be immediately visible across threads, such as a stop flag or a safely published reference. It prevents threads from keeping a stale cached value and it also prevents certain harmful reordering around that variable. But it does not give me mutual exclusion, so it is not enough for compound actions like incrementing a counter; for that I would use synchronized or an atomic class.
volatile really meansDetailed Explanation: A volatile field is a shared variable with special visibility rules. When one thread writes to it, other threads are guaranteed to observe that write when they next read the same field, instead of using a stale value that was cached in a register, CPU cache, or optimized away by the JIT compiler. This is part of the Java Memory Model, the rulebook that defines what changes one thread must be able to see in another thread.
volatile field.volatile write become visible first, then the volatile write becomes visible.volatile field and is forced to observe the most recent value that was published.volatile is perfect for state flags and safe publication, but not for multi-step updates.volatile boolean running.count++ is read-modify-write, so it can lose updates.volatile List<?> makes the reference visible, not the list contents thread-safe.| Feature | volatile | synchronized | AtomicInteger |
|---|---|---|---|
| Visibility | Yes | Yes | Yes |
| Atomicity | No | Yes | Yes for one value |
| Mutual exclusion | No | Yes | No |
| Best for | Flags, refs | Critical sections | Counters, CAS loops |
| Cost | Very low | Higher | Low-medium |
Performance note: a volatile read/write is usually much cheaper than taking a lock, often just a few nanoseconds on a modern JVM/CPU, but it is still slower than a plain field. Its complexity is constant-time, O(1), because there is no queueing or blocking like a lock can have. Since Java 5, the memory-model rules around volatile are strong and reliable; modern interview answers should mention visibility + ordering, not just “it goes to main memory.”
Important edge cases: the default value of a volatile field is still the normal default for its type, such as false for a boolean or 0 for an int. A volatile reference makes the reference update visible, but it does not freeze or synchronize the object it points to. If you need both visibility and atomic read-modify-write, use an atomic class or synchronization.
Real-World Story: Imagine a payment checkout service that runs many worker threads processing orders. During deployment, an admin flips a shutdown flag so the service can stop accepting new requests and drain safely. If that flag is plain boolean instead of volatile boolean, some worker threads may keep seeing the old true value and never stop.
What goes wrong in production: the new pod hangs during shutdown, the deployment times out, and the autoscaler keeps waiting for the old instance to terminate. Logs show the shutdown request was received, but the worker loop never exits; CPU may stay high because the loop is still spinning. Users might not notice immediately, but operators will see stuck rollouts, slow drain times, and repeated termination warnings.
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
public class VolatileKeywordDemo {
// A volatile flag is the classic use case: one thread publishes a change,
// other threads must see it without extra locking.
private volatile boolean running = true;
// This shows the key gotcha: volatile does NOT make ++ atomic.
private volatile int volatileCounter = 0;
private final AtomicInteger atomicCounter = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
VolatileKeywordDemo demo = new VolatileKeywordDemo();
demo.stopFlagDemo();
demo.atomicityDemo();
}
private void stopFlagDemo() throws InterruptedException {
Thread worker = new Thread(() -> {
long loops = 0;
// If running were not volatile, the JVM could legally keep using a stale
// cached value here, and this loop might never notice the stop request.
while (running) {
loops++;
}
System.out.println("Worker stopped after " + loops + " iterations.");
}, "worker");
worker.start();
Thread.sleep(200);
running = false; // volatile write: the worker is guaranteed to observe this change.
worker.join(1000);
if (worker.isAlive()) {
System.out.println("Unexpected: worker is still alive. That suggests a visibility bug.");
worker.interrupt();
} else {
System.out.println("Stop-flag demo finished normally.");
}
}
private void atomicityDemo() throws InterruptedException {
final int threads = 8;
final int incrementsPerThread = 100_000;
final int expected = threads * incrementsPerThread;
CountDownLatch start = new CountDownLatch(1);
CountDownLatch done = new CountDownLatch(threads);
for (int i = 0; i < threads; i++) {
Thread t = new Thread(() -> {
try {
start.await();
for (int j = 0; j < incrementsPerThread; j++) {
volatileCounter++; // Not atomic: read + add + write.
atomicCounter.incrementAndGet(); // Atomic single-variable update.
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
done.countDown();
}
}, "inc-" + i);
t.start();
}
start.countDown();
done.await();
System.out.println();
System.out.println("Atomicity demo:");
System.out.println("Expected count : " + expected);
System.out.println("volatileCounter : " + volatileCounter);
System.out.println("AtomicInteger : " + atomicCounter.get());
if (volatileCounter != expected) {
System.out.println("Race detected: volatile did not make ++ safe.");
} else {
System.out.println("This run did not lose updates, but the code is still unsafe.");
}
}
}Follow-up & Tricky Questions:
volatile instead of synchronized? Use volatile for a single shared flag or a safely published reference when you only need visibility and ordering. Use synchronized when you need a critical section, mutual exclusion, or multiple fields to change together.volatile make count++ thread-safe? No. Increment is a three-step read-modify-write operation, so two threads can read the same value and overwrite each other’s updates.volatile for double-checked locking? Yes, but only if the shared instance field is volatile. Since Java 5, that pattern is safe because volatile prevents the dangerous reordering that used to break it.volatile make the object it points to thread-safe? No. It only makes the reference update visible; if the object is mutable, you still need proper synchronization for its internal state.long and double, but that is different from making a compound operation atomic.AtomicInteger, LongAdder, or a lock depending on the contention pattern.Common Mistakes:
volatile for ++ or other updates. Correction: use AtomicInteger, LongAdder, or synchronized.Memory Hook: Think of volatile as a glass office door: everyone can see the latest note on the door, but the door does not stop two people from writing at the same time.
Cheat Sheet:
volatile = visibility + ordering, not mutual exclusion.synchronized for locks and critical sections.AtomicInteger/LongAdder for safe numeric updates.Practice Tasks:
volatile boolean flag.volatile int counter with AtomicInteger and compare results.volatile.