Thread Safety with volatile Keyword in Java

Illustration for Thread Safety with volatile Keyword in Java
By Last updated:

When working with multiple threads, one of the trickiest issues developers face is visibility — ensuring that changes made by one thread are seen by others. The volatile keyword in Java is a lightweight tool to tackle this problem.

This tutorial breaks down how volatile works, when to use it, and when it’s not enough.


⚙️ What Does volatile Do?

The volatile keyword ensures that:

  • The variable's value is always read from main memory (not thread-local cache).
  • All writes to the variable are immediately visible to other threads.
private volatile boolean running = true;

🚥 Where It Fits in Thread Lifecycle

  • Threads in RUNNING state may cache variable values.
  • volatile ensures up-to-date values are used when threads re-check conditions or flags.

State transitions: NEW → RUNNABLE → RUNNING → BLOCKED/WAITING → TERMINATED


🧠 Java Memory Model and Visibility

Java threads may use CPU caches for performance, which can cause stale reads. The Java Memory Model (JMM) introduces happens-before relationships — and volatile guarantees that:

  • A write to a volatile field happens-before every subsequent read of that field.

This ensures visibility, but not atomicity.


🚫 What volatile Does NOT Do

  • It does not make compound actions (like x++) atomic.
  • It does not replace synchronized when mutual exclusion is needed.

Example of unsafe usage:

volatile int count = 0;
count++; // Not atomic!

Use AtomicInteger or synchronization instead.


🔄 Correct Usage of volatile

Stop Flag Example

class Worker extends Thread {
    private volatile boolean running = true;

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

    public void stopWorker() {
        running = false;
    }
}

Configuration Reload Flag

Used to trigger background threads to reload settings without locking.

Status Monitoring

Communicate between threads with status flags like isShutdown, hasFailed, etc.


🧰 Alternatives to volatile

Requirement Best Option
Visibility only volatile
Atomic + visibility AtomicInteger
Mutual exclusion synchronized, ReentrantLock
Complex coordination Condition, BlockingQueue

📌 What's New in Java Concurrency (8–21)

  • Java 8: CompletableFuture, lambdas, parallel streams
  • Java 9: Flow API (reactive streams)
  • Java 11: Improvements to CompletableFuture, better visibility rules
  • Java 21: Virtual threads, structured concurrency, scoped values

🛠 Best Practices

  • Use volatile for flags or single-value reads/writes.
  • Avoid for counters, maps, or shared data structures.
  • Prefer atomic classes when atomicity is required.
  • Document volatile usage clearly for maintainability.

❓ FAQ

  1. Does volatile guarantee atomicity?
    No — use AtomicInteger or locks for that.

  2. Can I use volatile on reference types?
    Yes — the reference is volatile, not the object it points to.

  3. Is volatile better than synchronized?
    Not always — it's lighter but more limited.

  4. Can volatile cause performance issues?
    Slightly, due to memory fencing and main memory access.

  5. Does volatile work with double or long?
    Yes — Java ensures 64-bit types are safely published with volatile.

  6. How is volatile different from AtomicInteger?
    AtomicInteger ensures atomicity and visibility; volatile ensures only visibility.

  7. Is volatile thread-safe?
    Only for read/write, not for compound operations.

  8. Can I use volatile in enums?
    Yes, for enum fields if needed.

  9. Does volatile prevent instruction reordering?
    Yes, it acts as a memory barrier in the JVM.

  10. Can I synchronize on a volatile field?
    Yes, but the volatile modifier doesn't affect locking behavior.


🧾 Conclusion and Key Takeaways

  • Use volatile to ensure visibility between threads.
  • It's ideal for flags and signaling but not for compound updates.
  • Understand the limits of volatile and pair it with stronger tools when needed.
  • Mastering memory visibility is crucial for writing safe concurrent code.

Understanding volatile deepens your grasp of the Java Memory Model and helps you build performant, bug-free, and responsive applications.