How Volatile Keyword Works in Java Threading

The volatile keyword in Java is used to indicate that a variable’s value will be modified by different threads. When a variable is declared as volatile, it ensures visibility and ordering guarantees in a multi-threaded environment. This means changes made to a volatile variable by one thread are immediately visible to all other threads.

Key Points About volatile in Java:

  1. Visibility:
  • The volatile keyword ensures that the most recent write to a volatile variable is always visible to other threads.
  • Without volatile, the value of a variable might be cached by threads in local memory (e.g., CPU registers or thread-local cache), leading to inconsistent visibility of changes between threads.
  1. No Caching:
  • A volatile variable is not cached locally in any thread, and all reads and writes go directly to main memory.
  • This prevents threads from using stale values stored in their local caches.
  1. Atomicity (for certain operations):
  • For simple read and write operations (like int or boolean), volatile ensures atomic access. However, volatile does not guarantee atomicity for compound actions (like incrementing a value or checking and updating a value in one step).
  1. Ordering (Happens-Before Guarantee):
  • The volatile keyword establishes a happens-before relationship. This means:
    • A write to a volatile variable happens-before subsequent reads of that variable.
    • All writes to variables before writing to the volatile variable are visible to all threads that read the volatile variable.
  • This ensures that if a thread writes to a volatile variable, any memory writes (to other variables) made before that write are visible to other threads after they read the volatile variable.

Example of Using volatile:

class SharedData {
    private volatile boolean flag = false;

    public void writer() {
        flag = true; // Write to the volatile variable
    }

    public void reader() {
        if (flag) {
            // Code here will execute only when the volatile flag is true
            System.out.println("Flag is true");
        }
    }
}

In the above example:

  • The flag is declared as volatile. When one thread sets flag to true, another thread reading flag will see the updated value immediately, preventing issues with stale values.
  • Without volatile, the value of flag might be cached, and another thread might not see the updated value immediately.

When to Use volatile:

  • Use volatile when you have a variable that is shared between threads, and you need to ensure visibility across those threads without using locks.
  • Common use cases include flags, state variables, or control signals that are written by one thread and read by others.

Limitations of volatile:

  1. No Protection for Compound Actions: volatile does not provide synchronization for compound actions like incrementing a value (count++) or checking and updating (if (x == 0) x = 1;). For such cases, use synchronized blocks or Atomic classes (AtomicInteger, AtomicReference, etc.) to ensure atomicity.
  • Example of unsafe compound operation: class UnsafeCounter { private volatile int count = 0; public void increment() { count++; // Not atomic, leads to race condition } } In this case, count++ is a compound action involving both reading and writing the variable. Even though count is volatile, multiple threads can still increment the counter simultaneously, leading to race conditions.
  1. Performance Overhead: While volatile ensures visibility, it may add overhead as the variable is always read and written from main memory. For performance-critical scenarios, carefully consider whether the volatile keyword is necessary or if locking mechanisms would be more appropriate.

Volatile vs. Synchronization:

  • volatile: Ensures visibility of changes across threads, but does not guarantee atomicity for compound operations.
  • synchronized: Ensures both visibility and atomicity, but incurs more overhead due to locking and potential contention between threads.

Here’s a practical example of using the volatile keyword in Java to ensure visibility of a shared variable between multiple threads:

Problem: Visibility Issue Without volatile

Let’s assume we have a simple program with two threads: one is updating a boolean flag and the other is continuously checking that flag. Without volatile, the change made to the flag by one thread may not be immediately visible to the other thread.

Code Without volatile:

class Task extends Thread {
    private boolean running = true;

    public void run() {
        while (running) {
            // Busy-wait loop (doing some task)
        }
        System.out.println("Thread stopped.");
    }

    public void stopTask() {
        running = false;  // Thread will exit the loop when running is set to false
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Task task = new Task();
        task.start();  // Start the thread

        Thread.sleep(1000);  // Main thread sleeps for 1 second

        task.stopTask();  // Set running to false, the thread should stop
        System.out.println("Requested stop.");
    }
}

Explanation of the Problem:

  • The running variable is not marked as volatile, which means the JVM may cache its value in the thread’s local memory.
  • The main thread sets running = false, but there’s no guarantee that the Task thread will see the updated value because it might keep reading the cached version (true).

This can result in the Task thread never exiting the loop, causing a visibility issue.

Fix Using volatile:

To solve this, we can mark the running variable as volatile. This ensures that every time the Task thread reads the running variable, it gets the most up-to-date value from the main memory.

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

    public void run() {
        while (running) {
            // Busy-wait loop (doing some task)
        }
        System.out.println("Thread stopped.");
    }

    public void stopTask() {
        running = false;  // Thread will exit the loop when running is set to false
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Task task = new Task();
        task.start(); 

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *