1. What do you think of volatile?

First of all, let’s take a look at the last picture:

As shown in the figure above, the Java memory model shows that each thread has its own working memory and a shared main memory.

For example, let’s say there are two threads whose code needs to read the value of the data variable. Both threads load the value of the data variable from main memory into their working memory, and then they can use that value.

Now, as you can see, each thread has loaded a copy of the variable data into its own working memory, so each thread can read data = 0.

In this way, during thread code running, values for data can be loaded directly from working memory, not from main memory.

The question then is, why do we have to have a working memory for each thread to hold a copy of the variable in order to read it? Can’t I just have the thread load the value of the variable from main memory every time?

Very simple! Because the thread runs the code corresponding to some instructions, is executed by the CPU! But every time the CPU executes an instruction, which is a huge chunk of code that we wrote, if we load it from main memory every time we need a value of a variable, performance will be poor!

So one idea I came up with was the idea of threads having working memory, sort of like a high-speed local cache.

In this way, the thread code can load a copy of the variable directly from its own local cache during execution, without loading the value of the variable from main memory, which can greatly improve performance!

But think about it, what’s the problem with that?

Imagine that thread 1 changes the value of the data variable to 1 and then writes the change to its local working memory. The data value in thread 1’s working memory is 1.

However, the data value in main memory is still 0! The data value in thread 2’s working memory is still 0. !

Then, while thread 1 is running, it can read data with the last value of 1, but thread 2 is running with the value of 0!

As a result, both Thread 1 and Thread 2 are actually operating on a variable called data, but after Thread 1 modifies the value of data, Thread 2 cannot see the value of the old copy in its local working memory!

This is the so-called visibility problem in concurrent Java programming:

When multiple threads are concurrently reading and writing to a shared variable, it is possible that one thread has changed the value of the variable, but other threads cannot see it! Not visible to other threads!

2. The role of volatile and the principle behind it

So what if we want to solve this problem? This is where Volatile comes in! You can solve this visibility problem perfectly by simply adding volatile to the data variable.

What happens to a code like the following, for example, when volatile is added?

public class Helloworld{ private volatile int data =0; // Thread 1 reads and modifies data; // Thread 2 reads data;

So what are the key functions here?

  1. If thread 1 modifies a data variable with volatile, it will force the last value of the data variable to be brushed back to main memory after modifying the value of its local working memory. The value of the data variable in main memory must immediately become the most recent value! The whole process is shown in the figure below:
  2. If there is a local cache of this data variable in the working memory of other threads at this time, that is, a copy of the variable, then the data variable cache in the working memory of other threads will be forced to expire directly, and it is not allowed to read and use again! The whole process is shown in the figure below:
  3. If thread 2 needs to read the data variable again while the code is running and tries to read it from the local working memory, it will find that the data = 0 has expired! At this point, he must reload the data variable from main memory with the most recent value! Then you can read the latest value, data = 1! The whole process is shown in the figure below:

Bingo! Well, volatile perfectly solves the problem of visibility in Java concurrency!

When a variable is modified with the volatile keyword, a thread forces the brush back to main memory as soon as it changes the value of the variable.

Then force the cache to expire in the local working memory of another thread, and finally force the latest value to be reloaded from main memory when another thread reads the value!

This ensures that if one thread changes the value of a variable, the other threads will see it immediately! This is how so-called volatile guarantees visibility!

3. Summary & Reminder

Volatile’s primary purpose is to ensure visibility and order. Volatile does not guarantee atomicity! In other words, volatile solves the problem that when one thread changes the value of a variable, other threads can immediately read the latest value. It solves the problem of visibility!

However, if multiple threads are changing the value of a variable at the same time, it is still possible for multiple threads to have concurrent safety issues, leading to incorrect data value modification. Volatile is not responsible for solving this problem, i.e. atomicity!

The atomicity problem is solved by relying on locking mechanisms such as synchronized and ReentrantLock.

All the above documents are transferred from teacher huperzia architecture course!!