Volatile vs Synchronized

In this post, I will talk about usage of volatile fields in Java and how volatile is different from synchronized.

Both volatile and synchronized are used in multi-threaded programs to get some degree of thread safety depending on the operations performed by different threads. Consider the following piece of code

class VolatileDemo {
  private int data;
}

int get() {
  return data;
}

void set(int val) {
  data = val;
}

Suppose get() is invoked by multiple threads and set() is called by only only thread. Since there is only one writer, we don’t have to worry about race conditions. However, considering a modern multi-core architecture, it is likely that the reader and writer threads could be running on different cores. Thus the most recent value of data might be sitting in CPU cache of the core that writer thread was running on. This can prevent reader threads from seeing the result of latest set() operation. This is a memory visibility problem from concurrency point of view where updates made to a shared object by one thread may not be visible to other threads.

How does volatile solve visibility problem?

For the scenario described above, declaring “data” field as volatile will help. Every time a volatile variable is updated, JVM executes a “store barrier” instruction effectively flushing the CPU caches and ensuring that updates are written to main memory. Reads of a volatile variable are also done on physical memory thus guaranteeing that readers will always see the updated value.

Using volatile for a field in Java also instructs the compiler not to optimize away the variable.

Some common uses of volatile

Let’s go through some common coding patterns in Java where volatile can be useful

Using volatile for a status change flag

Let’s say there is a thread that does some piece of work until it has been told to stop. There might be a top level thread that created a bunch of worker threads and is responsible for telling the worker threads to stop. The worker thread code might look like:

private volatile boolean done = false;

// some other thread tells this thread to stop
void stop() {
  done = true
}

void work() {
  // if done is not volatile, the worker thread may not see
  // the value of done as true if the read is done from processor
  // cache
  while (!done) {
     // do something
  }
}

In this case, declaring “done” as volatile allows the worker thread to see the update. This also highlights a situation where cache coherence implemented by the hardware wouldn’t have helped the worker thread in seeing the change in value to “done” through cache invalidation (and cache miss) if the compiler had optimized away the loop thinking “done” will never be set to true.

while (!false) {
   // do something
}

Using volatile in callbacks

Consider we are writing some test that uses a library to register one or more callback functions. The test code essentially implements an interface of callback functions and performs some actions by calling the APIs of library — as a result of which some callbacks will be invoked by the library into the test code.

class Test {
  private volatile int callbackOneStat;
  private volatile int callbackTwoStat

  @Test
  void test() {
    library.registerCallbacks(new CallbackObject());
    library.performActions();
    // guarantees callbacks are invoked
    library.waitForActionsToComplete(); 
    assertEquals(10, callbackOneStat);
    assertEquals(20, callbackTwoStat);
  }

  private class CallbackObject implements Callback {

    @Override
    void callbackOne(Action action, int value) {
      callbackOneStat = value;
    }

    @Override
    void callbackTwo(Action action, int value) {
      callbackTwoStat = value;
    }
  }
}

Since the callbacks are invoked by a thread from library, declaring the stats as volatile guarantees that the thread running test code will see the change in value.

Using volatile as an execution indicator

With the new Java Memory Model, volatile also prevents any instruction reordering (by compiler or CPU). More specifically:

  • The non-volatile writes and reads that happen before a volatile write as per the program order cannot be reordered to happen after the volatile write.
    • This implies that once a write to volatile variable V is completed by thread A, the writes to all variables visible to A up until the write of V are also made visible in memory. Thus another thread B sees the updated value of not only the volatile field V, but also of the other non volatile fields (that were visible to thread A when volatile write to V was completed)
  • Similarly, non-volatile reads and writes that happen after volatile read as per the program order cannot be reordered to happen before the volatile read.

This guarantee (also referred to as happens-before relationship in Java) extends the usage of volatile field as an indicator of what operations have indeed completed. A commonly used scenario is to check if certain piece of code is initialized — where the initialization of member variables and data structures is done in one thread and the actual work is done in other thread but is dependent on initialization having properly completed.

class VolatileDemo {
  private Map<String, String> hashMap; 
  private List<Reader> readers;
  private volatile boolean initDone = false;

  // some other thread does this
  void init() {
    // initialize and/or populate hashMap
    // initialize/create readers
    initDone = true;
  }

  // different thread does the work
  void work() {
     while (!initDone) {
       // sleep
     }
     // use the initialized map and readers
  }
}

Java Memory Model will not allow any of the initialization statements to be reordered after the write to volatile variable “initDone”. Therefore if the thread running the work() function is actually seeing the value of initDone as true, it is guaranteed that map and readers are initialized and the worker thread is seeing the initialized data structures.

Since the initialization of map (including the initialization of objects in the map) and initialization of readers definitely happens before the volatile write in initializer thread and read of map and readers definitely happens in worker thread after the volatile read, we can conclude that all initialized data is visible in worker thread.

Old Java Memory Model only ensured that volatile reads and writes cannot be reordered with other volatile reads and writes and there were absolutely no guarantees w.r.t operations on non-volatile variables. This prevented the use of volatile in above cases.

When is volatile enough?

The usage of volatile as a synchronization construct is fairly limited. It provides a subset of properties provided by synchronized. With synchronized method or block, we get:

  • Mutual exclusion and thus no race conditions for any kind of operations on shared data
  • Memory visibility and happens before relationship – a monitor unlock by a thread is guaranteed to happen before a monitor lock (on the same lock object) by another thread.

Now in case of volatile, we only get the second property. Volatile doesn’t provide mutual exclusion and therefore if two threads are writing to shared data and the change to data is dependent on the current value of the data or any other global shared state of the program, using volatile is not enough for ensuring correct behavior and consistency of data. Synchronization should be used in such cases

Another example where volatile is not suitable would be implementing a thread safe counter which follows the read-modify-write pattern since the increment operator is actually doing multiple operations underneath. The correct way to implement such a thread safe counter is to use AtomicInteger, AtomicLong provided by Java concurrent library.

Volatile is enough when:

Multiple readers and single writer scenario:

  • Declaring volatile will do the job no matter what kind of updates are being made to the shared data.

Multiple readers and multiple writers scenario:

  • Update to shared data should not be dependent on the current value of the data and any other state of the program.
  • If the nature of updates to shared data do not have the above invariant, then synchronization should be used

We should carefully assess the nature of updates to shared data and if it can be updated by multiple threads (mostly true in all general cases) and then decide if volatile can be used instead of synchronization.

2 thoughts on “Volatile vs Synchronized

Add yours

Leave a comment

Blog at WordPress.com.

Up ↑