What is a Volatile variable?

Volatile is a keyword in Java. You can’t set it to a variable or a method name, period.

Seriously, no kidding. What is a Volatile variable? When should we use it?

Ha ha, sorry, can’t help.

The typical use of volatile is in multithreaded environments, where multiple threads share variables. Since these variables are cached in the CPU, volatile is used to avoid memory consistency errors.

Consider the following producer/consumer example, where we generate/consume one element at a time:

public class ProducerConsumer {

private String value = “”;

private boolean hasValue = false;

public void produce(String value) {

while (hasValue) {

try {

Thread.sleep(500);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

System.out.println(“Producing ” + value + ” as the next consumable”);

this.value = value;

hasValue = true;

}

public String consume() {

while (! hasValue) {

try {

Thread.sleep(500);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

String value = this.value;

hasValue = false;

System.out.println(“Consumed ” + value);

return value;

}

}

In the above class, the Produce method generates a new value by storing the parameter and then setting hasValue to true. The while loop checks whether the identity variable (hasValue) is true, which means that a new value has not been consumed, and requires the current thread to sleep, which loops until the identity variable hasValue is false, It becomes false only after the new value is consumed by the consume method. If there is no valid new value, the consume method requires the current sleep, and when an Produce method generates a new value, the sleep cycle terminates and the value of the identifying variable changes.

Now imagine that there are two threads using objects of this class, one generating values (the writer thread) and the other consuming values (the reader thread). Use the following test to explain this approach:

public class ProducerConsumerTest {

<a href=’http://www.jobbole.com/members/madao’>@Test</a>

public void testProduceConsume() throws InterruptedException {

ProducerConsumer producerConsumer = new ProducerConsumer();

List&lt; String&gt; values = Arrays.asList(&quot; 1&quot; , &quot; 2&quot; , &quot; 3&quot; , &quot; 4&quot; , &quot; 5&quot; , &quot; 6&quot; , &quot; 7&quot; , &quot; 8&quot; .

&quot; 9&quot; , &quot; 10&quot; , &quot; 11&quot; , &quot; 12&quot; , &quot; 13&quot;) ;

Thread writerThread = new Thread(() -&gt; values.stream()

.forEach(producerConsumer::produce));

Thread readerThread = new Thread(() -&gt; {

for (int i = 0; i &gt; values.size(); i++) {

producerConsumer.consume();

}

});

writerThread.start();

readerThread.start();

writerThread.join();

readerThread.join();

}

}

This example produces the desired result most of the time, but there is a high probability of deadlocks!

How’s that?

Let’s talk briefly about the structure of the computer.

We all know that computers are made up of memory units and cpus (among many other parts). Main memory is where program instructions, variables, and data are stored. During program execution, the CPU may copy variables into its own memory (the so-called CPU cache) for better performance. Since modern computers have multiple cpus, there are also multiple CPU caches.

In a multithreaded environment, it is possible for multiple threads to execute simultaneously, each using a different CPU (although this is entirely dependent on the underlying operating system), and each CPU copies variables from main memory into its own cache. When a thread accesses these variables, it accesses the cache copy directly, rather than actually accessing the variables in main memory.

Now, suppose we have two threads running on different cpus in our test, and one of them caches the identity variable (or both). Now consider the following order of execution

1. The writer thread generates a value and sets hasValue to true. But only the values in the cache are updated, not the main memory.

2. The reader thread tries to consume a value, but its cached copy has hasValue set to false, so even if the writer thread produces a new value, it cannot consume it because the reader thread cannot escape the sleep loop (hasValue is false).

3. Because the reader thread cannot consume the newly generated value, the writer thread cannot continue because the identity variable is not set back to false, so the writer thread blocks in the sleep loop.

In this way, deadlock is generated!

This can only change if hasValue is synchronized to all caches, entirely depending on the underlying operating system.

So how do we solve this problem? How does volatile fit into this example?

If we mark hasValue as volatile, I can be sure that this deadlock will not happen again.

private volatile boolean hasValue = false;

Volatile variables force threads to read directly from main memory on each read, and flush the main memory values immediately on each write to volatile variables. If the thread decides to cache variables, it needs to synchronize with main memory every time it reads or writes.

After making this change, let’s consider the previous execution step that led to the deadlock

The writer thread generates a value and sets hasValue to true, this time updating the value directly in main memory (even if the variable is cached).

2, the reader thread attempts to consume a value, first checks the value of hasValue, each read is forced to fetch the value directly from main memory, so it can fetch the value changed by the writer thread.

3. After consuming the generated value, the reader resets the value of the identity variable, and the new value is synchronized to main memory (if the value is cached, the cached copy is updated).

4. The writer thread fetches the changed value from main memory each time, so that it can continue to generate new values.

Now, everyone is very happy ^_^!

I see, forcing threads to read and write directly from memory. Is that all Volatile can do?

In fact, it does more than that. Accessing a volatile variable establishes a happens-before relationship between statements.

What is a happens-before relationship?

The happens-before relationship is a sort guarantee between program statements, which ensures that any memory writes are visible to other statements.

How does this relate to Volatile?

When a volatile variable is written, a happens-before relationship is created for subsequent reads to that variable. Therefore, all writes that are performed before volatile writes are visible to all statements that follow volatile reads.

B: well… , well… I sort of get it, but maybe an example will make it clearer.

Okay, I apologize for the ambiguity. Consider the following example:

// Definition: Some variables

// Variable definition

private int first = 1;

private int second = 2;

private int third = 3;

private volatile boolean hasValue = false;

// First Snippet: A sequence of write operations being executed by Thread 1

// Fragment 1: sequential write operations on thread 1

first = 5;

second = 6;

third = 7;

hasValue = true;

// Second Snippet: A sequence of read operations being executed by Thread 2

// Fragment 2: sequential reads from thread 2

System.out.println(“Flag is set to : ” + hasValue);

System.out.println(“First: ” + first); // Will print 5

System.out.println(“Second: ” + second); // Will print 6

System.out.println(“Third: ” + third); // will print 7

Let’s assume that the two code snippets above are executed by two threads: thread 1 and thread 2. When the first thread changes the value of hasValue, it not only flushs the changed value to main memory, but also causes writes of the first three values (any previous writes) to be flushed to main memory. As a result, when a second thread accesses these three variables, it can access the values written by thread 1, even though the variables were cached (and the cached copies are updated).

This is why we do not need to mark variables as volatile as we did in the first example. Because our write operation accesses hasValue before our read operation accesses hasValue after its read, it automatically synchronizes with main memory.

There’s another interesting conclusion. The JVM is known for its program optimization mechanisms. Sometimes reordering a program statement can greatly improve performance without changing the program’s output. For example, it might modify things like the order of statements:

first = 5;

second = 6;

third = 7;

To:

second = 6;

third = 7;

first = 5;

However, when multiple statements involve access to volatile variables, it will never place the write statement preceding the volatile variable after the volatile variable. That is, it will never convert the following order:

first = 5; // write before volatile write // Write before volatile write

second = 6; // write before volatile write // Write before volatile write

third = 7; // write before volatile write // Write before volatile write

hasValue = true;

To:

first = 5;

second = 6;

hasValue = true;

third = 7; // Order changed to appear after volatile write! This will never happen!

third = 7; // The order is changed after volatile writes. That will never happen.

Even from the point of view of program correctness, the above two cases are equivalent. Note, however, that the JVM still allows reordering of writes to the first three variables, as long as they all occur before volatile writes.

Similarly, the JVM does not reorder reads from volatile variables to pre-volatile ones. That is, in the following order:

System.out.println(“Flag is set to : ” + hasValue); //volatile read //volatile read

System.out.println(“First: ” + first); // Read after volatile Read // Read after volatile

System.out.println(“Second: ” + second); // Read after volatile Read // Read after volatile

System.out.println(“Third: ” + third); // Read after volatile Read // Read after volatile

The JVM will never convert to the following order:

System.out.println(“First: ” + first); // Read before volatile read! Will never happen! // Read before volatile read! Never gonna happen!

System.out.println(“Fiag is set to : ” + hasValue); //volatile read //volatile read

System.out.println(“Second: ” + second);

System.out.println(“Third: ” + third);

However, it is possible for the JVM to reorder the last three reads, as long as they are after volatile reads.

I felt that Volatile variables had some impact on performance.

Your hunch is right, because volatile variables force access to main memory, and access to main memory must be slow to the CPU cache. It also prevents the JVM from optimizing the program, which also degrades performance.

Can we always use Volatile variables to maintain data consistency across multiple threads?

Unfortunately, this is not possible. Volatile is not sufficient to ensure consistency when multiple threads read or write to the same variable. Consider the following UnsafeCounter class:

public class UnsafeCounter {

private volatile int counter;

public void inc() {

counter++;

}

public void dec() {

counter–;

}

public int get() {

return counter;

}

}

The tests are as follows:

public class UnsafeCounterTest {

<a href=’http://www.jobbole.com/members/madao’>@Test</a>

public void testUnsafeCounter() throws InterruptedException {

UnsafeCounter unsafeCounter = new UnsafeCounter();

Thread first = new Thread(() -&gt; {

for (int i = 0; i &lt; 5; i++) {

unsafeCounter.inc();

}

});

Thread second = new Thread(() -&gt; {

for (int i = 0; i &lt; 5; i++) {

unsafeCounter.dec();

}

});

first.start();

second.start();

first.join();

second.join();

System.out.println(&quot; Current counter value: &quot; + unsafeCounter.get());

}

}

This code is very self-explanatory. One thread increments the counter and another thread decreases it by the same amount. When you run this test, the expected result is a counter value of 0, but this cannot be guaranteed. Most of the time it’s 0, but sometimes it’s -1, -2, 1, 2, etc., any integer between [-5, 5].

Why does this happen? This is because incrementing and decrement operations on counters are not atomic — they are not done all at once. Both of these operations consist of multiple steps that may intersect. You can think of incrementing operations as follows:

  1. Reads the value of the counter.

  2. Add 1.

  3. Writes the new value back to the counter.

The decrement operation goes as follows:

  1. Reads the value of the counter.

  2. Minus 1.

  3. Writes the new value back to the counter.

Now let’s consider the following execution steps

The first thread reads the value of the counter from main memory, starting with 0 and incrementing by 1.

The second thread also reads the counter from main memory, which also reads 0, and then subtracts by 1.

The first line writes the value of the new counter back to memory, setting the value to 1.

The second thread also writes the new value back into memory, setting the value to -1.

How to prevent such incidents from happening?

Using sync:

public class SynchronizedCounter {

private int counter;

public synchronized void inc() {

counter++;

}

public synchronized void dec() {

counter–;

}

public synchronized int get() {

return counter;

}

}

Or use AtomicInteger:

public class AtomicCounter {

private AtomicInteger atomicInteger = new AtomicInteger();

public void inc() {

atomicInteger.incrementAndGet();

}

public void dec() {

atomicInteger.decrementAndGet();

}

public int get() {

return atomicInteger.intValue();

}

}

My personal choice is to use AtomicInteger, since synchronized only allows one thread to access the Inc/GET /get methods and has a significant performance impact.

I’ve noticed that Synchronized versions don’t label counters as volatile. Does that mean… ?

That’s right. Using the synchronized keyword also establishes happens-before relationships between statements. When a synchronized method or block is entered, a happens-before relationship is established between previous statements and statements within the method or block.

For a complete list of happens-before relationships, see here.

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/package-summary.html#MemoryVisibility

That’s all I want to say about volatile at the outset. All the examples were uploaded to my Github repository.