Java.Core.What is the purpose of the volatile keyword in Java?

The volatile keyword is a critical part of Java’s memory model, especially in multi-threaded programming. Let me explain what it does, when to use it, and its key limitations.


🚀 What is volatile?

volatile is a keyword that can be applied to a variable (field) in Java to ensure visibility of changes across threads. It tells the JVM that:

Every read of this variable must fetch the latest value directly from main memory (RAM).
Every write to this variable must immediately flush to main memory.


🔥 Why is this needed?

In Java, to improve performance, each thread caches variables in its own thread-local cache (CPU registers, L1/L2 cache, etc.) instead of reading them from main memory every time. This can lead to stale data in multi-threaded programs.

Example (Without volatile):

private boolean running = true;  // shared variable between threads

public void stop() {
    running = false;
}

public void run() {
    while (running) {
        // do some work
    }
}

One thread calls stop(), setting running = false.

Another thread is running the while (running) loop.

That second thread might never see the updated running value, because running might be cached in that thread’s local cache.

This is called the visibility problem.

✅ Solution: Use volatile

private volatile boolean running = true;  // Now changes are visible to all threads
  • Now, any change to running is immediately visible to all threads.
  • Every thread reads the fresh value directly from main memory.

💡 Key Point: volatile only solves visibility, not atomicity.


🔥 Visibility vs Atomicity — Important!

PropertyWhat it meansSolved by volatile?
VisibilityChanges made by one thread are visible to others immediately.✅ Yes
AtomicityCompound actions (like count++) happen as a single indivisible step.❌ No

🚨 Example where volatile is NOT enough

private volatile int count = 0;

public void increment() {
    count++;  // not atomic!
}
  • count++ looks like one operation, but it’s actually three steps:
    1. Read count.
    2. Increment the value.
    3. Write back count.
  • If two threads do this at the same time, they could both read 0, increment to 1, and write 1 back — losing one increment. This is called a race condition.
  • To fix this, you need synchronization (like synchronized block) or a java.util.concurrent.atomic class (AtomicInteger).

✅ When to use volatile

CaseUse volatile?
Simple flag that is written by one thread and read by others (like running)✅ Yes
Counter that is updated by multiple threads❌ No, use AtomicInteger or synchronized
Compound operations (check-then-act)❌ No, use locks

🛠️ Example Use Case (Good)

private volatile boolean shutdown = false;

public void run() {
    while (!shutdown) {
        // do work
    }
}

public void stop() {
    shutdown = true;  // no lock needed, all threads see this change
}

❌ Example Use Case (Bad)

private volatile int count = 0;

public void increment() {
    count++;  // not atomic, race condition!
}

This needs:

private final AtomicInteger count = new AtomicInteger();

public void increment() {
    count.incrementAndGet();
}

🔔 Quick Summary Table

KeywordVisibilityAtomicityUse Case
volatile✅ Yes❌ NoSimple flags, stop signals
synchronized✅ Yes✅ YesFull mutual exclusion
AtomicInteger✅ Yes✅ YesCounters, accumulators

💬 Final Tip

Think of volatile as a lighter, cheaper way to ensure visibility, but only for single variable reads/writes. For more complex operations (like increments or multiple variables needing consistent state), you need real locks.

✅ Example 1 — Correct Use of volatile

Scenario: Thread stop flag

  • One thread is running a loop.
  • Another thread can stop it.
class Worker extends Thread {
    private volatile boolean running = true;  // <-- volatile ensures visibility

    public void run() {
        while (running) {
            System.out.println("Working...");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        System.out.println("Stopped");
    }

    public void stopWorker() {
        running = false;  // immediately visible to the running thread
    }
}

public class VolatileExample {
    public static void main(String[] args) throws InterruptedException {
        Worker worker = new Worker();
        worker.start();

        Thread.sleep(2000);
        worker.stopWorker();  // without volatile, worker might never see this change
    }
}

❌ Example 2 — Wrong Use of volatile

Scenario: Increment counter

class Counter {
    private volatile int count = 0;

    public void increment() {
        count++;  // This is NOT atomic, even with volatile!
    }
}

What’s wrong?

  • count++ is a read-modify-write operation.
  • Two threads could read the same value, both increment, and both write back — losing one update.

✅ Correct approach:

class Counter {
    private final AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet();
    }
}

🔥 volatile vs synchronized

Featurevolatilesynchronized
Visibility✅ Guarantees visibility✅ Guarantees visibility
Atomicity❌ No atomicity✅ Full atomicity
ScopeOnly for single variable reads/writesCan protect any block of code
PerformanceFast — just a memory fenceSlower — full lock acquisition
Use CaseSimple flags (like stop signals)Critical sections (like increments, check-then-act logic)
This entry was posted in Без рубрики. Bookmark the permalink.