Volatile Keyword in Java

Naveen AnamaneniUpdated · 5 min read

The volatile keyword in Java marks a field whose value may be changed by one thread and read by others. It guarantees visibility: every write to a volatile field is seen by every later read from any thread. Without it, a thread may keep using an old copy of a value for a long time, because the JIT compiler and the CPU are allowed to cache and reorder plain reads and writes. However, volatile does not make compound actions like count++ atomic. In this lesson, you will see the visibility problem happen and learn exactly what volatile does and doesn't do.

Syntax

Syntax
private volatile boolean running = true;

The Visibility Problem

Each thread may see shared memory through caches and optimizations. When a field isn't volatile (or protected by a lock), the JIT compiler is allowed to read it once and reuse the value, for example turning while (!stop) { } into an endless loop that never checks again.

Example: A Loop That Never Sees the Change

Let's see an example with two identical loops, one reading a plain field and one reading a volatile field. The main thread sets both flags after half a second. The threads are daemons, so the program can end even if a loop never stops.

Visibility.java▶ Try it
public class Visibility {
    static boolean plainFlag = false;
    static volatile boolean volatileFlag = false;
 
    public static void main(String[] args) throws InterruptedException {
        Thread plain = new Thread(() -> {
            long spins = 0;
            while (!plainFlag) {
                spins++;
            }
        });
        Thread vol = new Thread(() -> {
            long spins = 0;
            while (!volatileFlag) {
                spins++;
            }
        });
        plain.setDaemon(true);
        vol.setDaemon(true);
        plain.start();
        vol.start();
 
        Thread.sleep(500);
        plainFlag = true;
        volatileFlag = true;
        plain.join(1000);
        vol.join(1000);
        System.out.println("Plain loop stopped?    " + !plain.isAlive());
        System.out.println("Volatile loop stopped? " + !vol.isAlive());
    }
}
Output
Plain loop stopped?    false
Volatile loop stopped? true

Explanation: After the plain loop ran for a while, the JIT compiler optimized it, reading plainFlag only once; the change made by the main thread was never seen, so that loop spins forever. The volatile read must see the latest write, so that loop stops. This result appeared on every run on the test machine, but because it depends on JIT optimization, a plain loop might stop on another JVM, which is exactly why such bugs are dangerous.

What volatile Does and Doesn't Do

volatile guaranteesvolatile does not guarantee
Visibility: reads see the latest writeAtomicity of count++ or check-then-act
No reordering of the volatile access with earlier writes (happens-before)Mutual exclusion between threads
Atomic reads and writes of long and doubleThread safety of the object a reference points to

Example: volatile Is Not Enough for count++

NotAtomic.java▶ Try it
import java.util.concurrent.atomic.AtomicInteger;
 
public class NotAtomic {
    static volatile int volatileCount = 0;
    static final AtomicInteger atomicCount = new AtomicInteger();
 
    public static void main(String[] args) throws InterruptedException {
        Runnable work = () -> {
            for (int i = 0; i < 100_000; i++) {
                volatileCount++;
                atomicCount.incrementAndGet();
            }
        };
        Thread a = new Thread(work);
        Thread b = new Thread(work);
        a.start();
        b.start();
        a.join();
        b.join();
        System.out.println("volatile reached 200000? "
            + (volatileCount == 200_000));
        System.out.println("atomic reached 200000?   "
            + (atomicCount.get() == 200_000));
    }
}
Output
volatile reached 200000? false
atomic reached 200000?   true

Explanation: volatileCount++ is still a read, an add and a write; another thread can slip in between, so updates are lost even though every write is visible. AtomicInteger.incrementAndGet() performs the whole update as one atomic step.

Common Mistakes

Using volatile for counters. count++ still loses updates.

Reading a shared stop flag without volatile or a lock. The loop may never stop.

Making a reference volatile and assuming the object is thread-safe. Only the reference is covered.

Interview Corner

What does the volatile keyword do?

volatile marks a field so that every write to it is visible to subsequent reads by other threads and the compiler and CPU may not reorder accesses around it in ways that break this; formally, a write to a volatile field happens-before every later read of that field. It also makes reads and writes of long and double atomic. It doesn't provide mutual exclusion or make compound actions such as count++ atomic, so it's suited to flags and published references, while counters need atomics or locks.

Practice

  1. Easy: Write a worker thread that loops until a volatile boolean running is set to false by the main thread after one second.
  2. Harder: Remove volatile from your flag, run the program several times, and explain what you observe.

Quick check: volatile

4 questions
1What does volatile guarantee?
2Is volatileCount++ thread-safe?
3What is a good use of volatile?
4Without volatile or a lock, can a loop miss a change to its flag forever?

Key Takeaways

Was this tutorial helpful?

Lesson 408 of 482