preface

Before you understand what volatile is, it is important to know that volatile does not guarantee atomicity. It is simply a way for threads to collaborate with data in the Java memory model, solving the visibility problem in concurrent programming.

Two semantics of volatile

The volatile keyword has two semantics, the first is guaranteed visibility and the second is guaranteed order.

Guaranteed visibility

We know that in the Java memory model, we have main memory and working memory, working memory is private to the Java thread, and main memory is public to the thread, so the data is stored in main memory, and if the thread wants to read a variable, it reads it from main memory and loads it into its own thread’s working memory.

Then there is a visibility problem: Thread 1 and thread 2 read the value of a variable in main memory called data, and thread 1 changes the value of data, but thread 2 does not follow and keeps the old value of data.

This problem can be solved with the volatile keyword, which serves two purposes:

First, once volatile is added to a variable, only any thread that reads the variable and changes its value in its own working memory forces the variable’s latest value to be flushed back to main memory, making the variable’s value in the winner’s memory the latest value.

Second: the first function is not enough, so the second effect is that the working memory copy of the variable stored by other threads will be invalidated and expired, so that other threads will reload the variable if they still want to use it.

Ensure order

This semantics is implemented by disallowing instruction reordering and involves the more complex concepts of instruction reordering and memory barriers, which are briefly introduced.

(1) What is instruction reordering: from the point of view of hardware structure, instruction reordering refers to the CPU used to allow multiple instructions not in accordance with the order of the program to be developed to the corresponding circuit unit processing. However, this does not mean that instructions are arbitrarily rearranged. The CPU needs to be able to handle instruction dependencies correctly in order for the program to execute correctly. To put it simply, instead of executing instructions in code order, the CPU may internally adjust the code order to better suit the execution of the instructions. However, no matter how you adjust it, the execution of the instructions will end up matching the sequential execution of the code, so the reordering will still look orderly from the outside.

(2) Volatile prevents instruction reordering by setting an additional barrier operation, lock ADDL $0x0,(% ESP), that does not reorder subsequent instructions to the place before the barrier. The lock prefix of this instruction causes the local CPU’s Cache to be written to memory. This write action will also cause other cpus or other cores to invalidate their Cache. This operation is equivalent to a “store and write” operation on variables in the Cache. Because volatile variables require that changes in working memory be written back to main memory immediately after they occur. Changes to the volatile variable are immediately visible to other cpus through this null operation instruction.

The lock addL $0x0,(% ESP) instruction synchronizes changes to memory, meaning that all previous operations have been performed, thus creating the effect that instruction reordering cannot cross the memory barrier.

The read cost of volatile is almost the same as that of normal variables, but the write may be slower because it requires memory barriers to be inserted into the native code to keep the processor from executing out of order