View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All

Volatile Keyword in Java: Complete Guide with Practical Examples

Updated on 13/05/20255,004 Views

In Java, when multiple threads access shared data, things can quickly get tricky. One thread might change a variable, but others might not immediately see the updated value. This is where the volatile keyword comes in. It helps ensure that all threads see the most recent value of a variable.

The volatile keyword is a part of Java’s concurrency toolbox. While it doesn’t replace synchronization, it plays a vital role in improving memory visibility and avoiding unexpected behavior in multi-threaded programs.

In this blog, you'll learn what volatile means in Java, how it works behind the scenes, and when to use it—with clear explanations and working code examples.

Want to build stronger Java skills? Online software engineering courses can help you learn faster and better.

What is Volatile Keyword in Java?

The volatile keyword in Java programming is a field modifier used primarily to indicate that a variable's value may be changed by multiple threads simultaneously. When a field is declared volatile, it tells the JVM that reads and writes to this variable should bypass the CPU cache and be performed directly on main memory.

Key Properties of Volatile Variables:

  1. Memory Visibility: Changes made to volatile variables by one thread are immediately visible to all other threads
  2. No Atomic Operations: While reads and writes of volatile variables are atomic, compound operations (like increment) are not atomic
  3. Prevents Reordering: The JVM cannot reorder instructions around volatile variable access
  4. No Thread Locking: Unlike synchronized, volatile doesn't use thread locking or create a monitor

Basic Syntax of Volatile in Java

public class VolatileExample {
    // Declaration of a volatile variable
    private volatile boolean flag = false;
    
    // Methods that use the volatile variable
    public void setFlag() {
        flag = true;  // Write directly to main memory
    }
    
    public boolean isFlag() {
        return flag;  // Read directly from main memory
    }
}

How Java Memory Model Works with Volatile

To understand the volatile keyword fully, we need to grasp the basics of the Java Memory Model (JMM):

  1. Main Memory: Shared across all threads and stores the actual variable values
  2. Working Memory (CPU Cache): Each thread has its own cache to improve performance
  3. Data Flow: Without volatile, threads may read/write values from their cache instead of main memory

The volatile keyword forces:

  • Every read of a volatile variable to be read from main memory (not from cache)
  • Every write to a volatile variable to be written to main memory (not just to cache)

Enhance your abilities through these best-in-class certifications.

When to Use Volatile in Java

The volatile keyword is appropriate in specific scenarios:

  1. Flag Variables: When a variable acts as a simple flag that can terminate a thread
  2. Status Indicators: When a variable indicates the status of a resource
  3. Without Compound Operations: When the variable's operations don't depend on its current value
  4. Single Writer Pattern: When only one thread updates the value, but multiple threads read it
  5. Double-Checked Locking: As part of the double-checked locking pattern in singleton implementations

Example 1: Using Volatile for Thread Termination

public class VolatileThreadTermination {
    // Without volatile, the thread might never terminate
    private volatile boolean running = true;
    
    public void stop() {
        running = false; // Signal the thread to stop
    }
    
    public void processData() {
        new Thread(() -> {
            while (running) {
                // Process data
                System.out.println("Processing data...");
                
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
            System.out.println("Thread terminated");
        }).start();
    }
    
    public static void main(String[] args) throws InterruptedException {
        VolatileThreadTermination processor = new VolatileThreadTermination();
        processor.processData();
        
        // Let the thread run for a while
        Thread.sleep(500);
        
        // Stop the thread
        processor.stop();
        System.out.println("Stop signal sent");
    }
}

Output:

Processing data...

Processing data...

Processing data...

Processing data...

Processing data...

Stop signal sent

Thread terminated

In this example:

  • The running variable is declared volatile to ensure visibility across threads
  • The main thread updates running to false, and this change is immediately visible to the worker thread
  • Without volatile, the worker thread might never see the updated value and could run indefinitely

Example 2: Volatile vs. Non-Volatile Variables

public class VolatileVisibilityDemo {
    private volatile boolean volatileFlag = false;
    private boolean nonVolatileFlag = false;
    
    // Thread to modify both flags
    public void startModifierThread() {
        new Thread(() -> {
            try {
                Thread.sleep(1000);
                volatileFlag = true;
                nonVolatileFlag = true;
                System.out.println("Flags modified by thread");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }).start();
    }
    
    // Method to check both flags
    public void checkFlags() {
        while (!volatileFlag) {
            // Wait until volatile flag becomes true
        }
        System.out.println("Volatile flag detected as true");
        
        // At this point, nonVolatileFlag might still be false in this thread's cache!
        if (nonVolatileFlag) {
            System.out.println("Non-volatile flag is also visible as true");
        } else {
            System.out.println("Non-volatile flag not visible yet, still false in this thread's view");
        }
    }
    
    public static void main(String[] args) {
        VolatileVisibilityDemo demo = new VolatileVisibilityDemo();
        demo.startModifierThread();
        demo.checkFlags();
    }
}

Output:

Flags modified by thread

Volatile flag detected as true

Non-volatile flag is also visible as true

Note: The actual output might vary depending on the JVM implementation and hardware. In some cases, the non-volatile flag might not be immediately visible, demonstrating the memory visibility issue.

Example 3: Implementing a Thread-Safe Singleton with Volatile

public class VolatileSingleton {
    // The volatile keyword is critical here
    private static volatile VolatileSingleton instance;
    
    // Private constructor prevents instantiation
    private VolatileSingleton() {
        System.out.println("Singleton instance created");
    }
    
    // Double-checked locking pattern
    public static VolatileSingleton getInstance() {
        if (instance == null) {
            synchronized (VolatileSingleton.class) {
                if (instance == null) {
                    instance = new VolatileSingleton();
                }
            }
        }
        return instance;
    }
    
    public void showMessage() {
        System.out.println("Singleton method called");
    }
    
    public static void main(String[] args) {
        // Create threads that attempt to get the singleton instance
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                VolatileSingleton singleton = VolatileSingleton.getInstance();
                singleton.showMessage();
            }).start();
        }
    }
}

Output:

Singleton instance created

Singleton method called

Singleton method called

Singleton method called

Singleton method called

Singleton method called

In this example:

  • The volatile keyword ensures that multiple threads handle the instance variable correctly
  • Without volatile, the singleton pattern could break due to instruction reordering
  • The double-checked locking pattern optimizes performance by only synchronizing when necessary

Limitations of Volatile in Java

While powerful, the volatile keyword has several important limitations:

  1. Not for Compound Operations: Operations like count++ (which is really count = count + 1) are not atomic even with volatile
  2. No Mutual Exclusion: Volatile doesn't provide locking or mutual exclusion
  3. Race Conditions: Volatile alone cannot prevent race conditions in complex scenarios
  4. No Grouping of Operations: Unlike synchronized blocks, volatile works on individual variables

Volatile vs. Synchronized: When to Use Each

Let's compare volatile with synchronized to understand their different use cases:

Feature

volatile

synchronized

Purpose

Memory visibility

Mutual exclusion

Scope

Applies to variables

Applies to code blocks or methods

Atomic operations

Only single read/write operations

All operations within the block

Performance impact

Lower overhead

Higher overhead

Thread blocking

No

Yes

Lock acquisition

No

Yes

Use case

Simple flags, status indicators

Complex operations requiring atomicity

Memory consistency

Only for the volatile variable

For all variables within the block

Volatile and the Happens-Before Relationship

The Java Memory Model defines the "happens-before" relationship, which is crucial for understanding volatile:

  1. A write to a volatile variable happens-before every subsequent read of that variable
  2. Actions before writing a volatile variable cannot be reordered after it
  3. Actions after reading a volatile variable cannot be reordered before it

This relationship creates a memory barrier that ensures orderly visibility of not just the volatile variable but also other variables modified before the volatile write.

Common Pitfalls When Using Volatile

Even experienced developers can misuse volatile. Here are common pitfalls to avoid:

  1. Using volatile for compound operations: Incrementing a volatile variable (volatile int counter++) is not atomic
  2. Relying on volatile for complex invariants: When multiple variables need to maintain consistency with each other
  3. Overusing volatile: Using volatile when not necessary adds overhead without benefits
  4. Confusing visibility with atomicity: Volatile ensures visibility but not atomicity
  5. Volatile arrays: Elements of a volatile array are not themselves volatile

Alternatives to Volatile in Java

When volatile isn't sufficient, consider these alternatives:

  1. AtomicInteger, AtomicLong, AtomicBoolean: For simple atomic operations
  2. ReadWriteLock: When you need read/write separation
  3. ReentrantLock: For more flexible locking than synchronized
  4. ConcurrentHashMap and other concurrent collections: For thread-safe collections
  5. synchronized blocks: For complex operations requiring atomicity

Performance Implications of Volatile

Using volatile has performance implications:

  1. Memory Overhead: Direct memory access is slower than cache access
  2. Prevents Optimizations: The JVM and CPU cannot apply certain optimizations
  3. Memory Barrier Costs: Creating memory barriers has a performance impact
  4. Still Faster Than Locks: Despite the overhead, volatile is faster than using synchronized

Best Practices for Using Volatile Keyword in Java

To use volatile effectively:

  1. Use for Simple Flags: Ideal for boolean flags that control thread behavior
  2. Consider Atomic Classes: For numeric values that need thread-safe mutations
  3. Document Thread Safety: Clearly document the thread safety guarantees
  4. Avoid Overuse: Only use volatile when necessary for memory visibility
  5. Combine with Other Techniques: For complex scenarios, use volatile as part of a broader concurrency strategy
  6. Test Thoroughly: Concurrency issues can be difficult to reproduce and debug
  7. Consider Higher-Level Concurrency Utilities: For complex concurrency requirements, use higher-level constructs from java.util.concurrent

Conclusion

The volatile keyword in Java is a powerful tool for ensuring memory visibility in multi-threaded applications. By understanding what the volatile keyword in Java does, how it works with the Java Memory Model, and when to use it, you can write more efficient and reliable concurrent code.

Remember that volatile is not a silver bullet for all concurrency issues—it's best suited for simple flags and status indicators where visibility, not atomicity, is the primary concern. For more complex scenarios, combine volatile with other concurrency tools or consider higher-level abstractions from the java.util.concurrent package.

By applying the knowledge, examples, and best practices outlined in this guide, you'll be well-equipped to use the volatile keyword effectively in your Java applications.

FAQ

1. What is the main purpose of the volatile keyword in Java?

The primary purpose of the volatile keyword is to ensure memory visibility across threads. When a variable is declared volatile, any thread reading the variable will see the most recently written value by any thread, as it forces operations to be performed on main memory rather than CPU caches.

2. Does volatile make operations atomic?

No, volatile only guarantees visibility of the latest value. It makes single reads and writes atomic but doesn't make compound operations (like increment) atomic. For atomic operations on a single variable, consider using classes from the java.util.concurrent.atomic package instead.

3. Can I use volatile with arrays?

Yes, you can declare an array as volatile, but this only makes the reference to the array volatile, not the array elements. Operations on the array elements are not guaranteed to be visible across threads unless you take additional synchronization measures.

4. How does volatile impact performance?

Volatile variables bypass CPU caches and operate directly on main memory, which can be slower than cache access. However, volatile is still much faster than using synchronized blocks. Use volatile when visibility is the only concern and synchronized when atomicity is required.

5. When should I use volatile instead of synchronized?

Use volatile when you only need visibility guarantees and not atomicity or mutual exclusion. Volatile is appropriate for simple flags, status indicators, and the double-checked locking pattern. Use synchronized for more complex scenarios requiring atomic operations or maintaining invariants across multiple variables.

6. Can final variables replace volatile in some cases?

Yes, final variables also provide visibility guarantees in Java. When a final field is initialized in a constructor, it's guaranteed that any thread that sees the object will also see the correct value of the final field. However, final can't be used when the variable needs to change after initialization.

7. Is volatile necessary in single-threaded applications?

No, volatile has no effect in single-threaded applications since there's no concern about memory visibility across multiple threads. Using volatile in such contexts only adds unnecessary overhead.

8. How does volatile work with the Java Memory Model?

The Java Memory Model (JMM) specifies that writes to a volatile variable establish a happens-before relationship with subsequent reads of that variable. This means that not only is the volatile variable itself visible, but all variables modified before writing to the volatile variable are also visible to other threads that read the volatile variable.

9. Can volatile prevent instruction reordering?

Yes, volatile prevents the reordering of instructions around volatile variable access. The JVM cannot reorder instructions in a way that would place a volatile read/write before instructions that precede it in the code, or after instructions that follow it in the code.

10. Does volatile work across multiple JVMs or distributed systems?

No, volatile only works within a single JVM. For distributed systems or applications running across multiple JVMs, you would need to use other mechanisms like distributed locks, databases, or message queues to achieve similar visibility guarantees.

11. Can I use volatile with primitive wrappers like Integer?

It's not recommended. When you use volatile with wrapper classes (like Integer, Long, etc.), the reference to the object is volatile, but the internal state is not. Since wrapper classes are immutable, operations like incrementing actually create new objects. Use AtomicInteger or similar classes instead for mutable, thread-safe numeric operations.

image

Take the Free Quiz on Java

Answer quick questions and assess your Java knowledge

right-top-arrow
image
Join 10M+ Learners & Transform Your Career
Learn on a personalised AI-powered platform that offers best-in-class content, live sessions & mentorship from leading industry experts.
advertise-arrow

Free Courses

Explore Our Free Software Tutorials

Disclaimer

1.The above statistics depend on various factors and individual results may vary. Past performance is no guarantee of future results.

2.The student assumes full responsibility for all expenses associated with visas, travel, & related costs. upGrad does not provide any a.