volatile

  • It is used on fields, both on primitive and reference types
  • Solves visibility problem
  • Any change in thread local variable will be reflected in other thread’s variable instantaneously
  • It is lock free and lightweight compared to synchronized
  • Locking can guarantee both visibility and atomicity, volatile variables can only guarantee visibility
  • When to use:
    • Writes to variable do not depend on its current value
    • You can ensure that only a single thread ever updates the value
  • References

Example

  • Single Thread Example
    • If we don’t use volatile, worker Thread may never terminate
public class Volatile {
    // volatile is needed here
    private static volatile boolean stop = false;
    
    public static void main(String[] args) throws InterruptedException {
        Thread worker = new Thread(() -> {
            int i = 0;
            while (!stop) {
                i++;
            }
            System.out.println("Stopped at i = " + i);
        });
        worker.start();
        Thread.sleep(1000);
        stop = true;
        System.out.println("stop set to true");
    }
}
  • Double Thread Example
    • If we don’t use volatile then readerThread will never read latest value and while loop never terminates in readerThread
public class Volatile {
    // volatile is needed here
    private static boolean flag = false;
 
    public static void main(String[] args) {
        Thread writerThread = new Thread(() -> {
            try {
                Thread.sleep(2000);
                System.out.println("Flag to set");
                flag = true;
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
 
        Thread readerThread = new Thread(() -> {
            while (!flag) {
                // busy waiting
            }
 
            flag = false;
            System.out.println("Flag is unset finally!");
        });
 
        writerThread.start();
        readerThread.start();
    }
}

Working (can be improved)

  • CPU stores the variable in cache, and sometimes after updating value in CPU cache, it does not immediately update the main memory (RAM) and also to other CPU cache
  • Hence other threads may not see the change instantaneously
  • volatile causes read and write to happen on main memory (RAM) directly instead of cache
  • Volatile variables are not cached in registers or in caches where they are hidden from other processors, so a read of a volatile variable always returns the most recent write by any thread
flowchart LR

cpu1["CPU"]
cpu2["CPU"]
cache1["Cache\n(variable)"]
cache2["Cache\n(variable)"]
ram["RAM\n(Variable)"]

Thread1 --> cpu1 --> cache1 --> ram
Thread2 --> cpu2 --> cache2 --> ram