Summary of Java keyword volatile knowledge points

In this article, the editor compiled for everyone is a summary of knowledge points about the Java keyword volatile, friends who are interested can learn and refer to it.
What is volatile ? The
volatile keyword is a lightweight synchronization mechanism provided by Java. It can guarantee visibility and order, but it cannot guarantee atomicity

Visibility
For the visibility of volatile, first look at the execution of this code

The flag defaults to true. Create a thread A to determine whether the flag is true. If it is true, after executing the i++ operation for two seconds, create another thread B and change the flag to false. Thread A does not perceive that the flag has been changed to false. What is the equivalent of jumping out of the loop
? It is equivalent to your goddess saying to you, you work hard, marry you if you earn one million a year, and after you listen, work hard to make money. After 3 years, your annual salary is one million, and you go back to find your goddess, and it turns out that your goddess is married. The news of her marriage was not told to you at all! Is it uncomfortable?

Goddess marriage can not tell you, but the attributes in the Java code are all stored in memory, why is the modification of one thread invisible to the other thread? This has to mention the memory model in Java. The memory model in Java, referred to as JMM, defines the abstract relationship between threads and main memory, and defines shared variables between threads to be stored in main memory. Each thread has a private local memory. The local memory stores a copy of the thread to read/write shared variables. It covers caches, write buffers, registers, and other hardware and compiler optimizations.

note! JMM is an abstract concept that shields the differences between different operating system architectures, and is just a set of Java specifications .

Knowing the JMM, now let’s review the code at the beginning of the article. Why does thread B modify the flag and thread A sees the original value?

Because thread A copies the flag=true at the beginning to local memory, then the flag used by thread A is the flag copied to local memory. After thread B modifies the flag, it flushes the value of the flag to the main memory. At this time, the flag value of the main memory becomes false. Thread A does not know that thread B has modified the flag, and always uses the flag = true of the local memory.
So, how can we let thread A know that the flag has been modified? In other words, how to invalidate the flag cached in the local memory of thread A so that it can be visible between threads? Modifying the flag with volatile can be done:

We can see that after modifying the flag with volatile, thread A can perceive it after thread B modifies the flag, which shows that volatile guarantees the visibility between thread synchronization.
Reordering
Before explaining the order of volatile, we need to add some knowledge about reordering.

Reordering refers to a means by which the compiler and processor reorder the instruction sequence in order to optimize the performance of the program .

Why is there a reordering? Simply put, it is to improve execution efficiency. Why can it improve execution efficiency? Let's look at the following example: we

can see that the CPU actually performs a read and write back operation after reordering, which indirectly improves the execution efficiency.
It must be emphasized that the example in the figure above is just to allow readers to better understand why reordering can improve execution efficiency. In fact, reordering in Java is not based on the code level. There are also between code and CPU execution. In many stages, there are some optimizations at the bottom of the CPU, and the actual execution process may not be as shown in the figure above. Don't worry too much about this.

Reordering can improve the efficiency of the program, but must follow the as-if-serial semantics. What is the semantics of as-if-serial? Simply put, no matter how you reorder, you must ensure that no matter how you reorder, the execution result of the program in a single thread cannot be changed.

Orderliness
We have already introduced Java have reordering case, and now we come back to chat with the orderly volatile.

Let's look at a classic interview question first: Why does DDL (double check lock) singleton mode need to add the volatile keyword?

Because singleton = new Singleton() is not an atomic operation, it probably goes through these steps:
allocate a memory space to call the constructor, and initialize the instance singleton to point to the allocated memory space

In actual execution, reordering may occur, resulting in the actual execution steps as follows:

Apply for a memory space singleton to point to the allocated memory space to call the constructor to initialize the instance

After the singleton points to the allocated memory space, the singleton is not empty. But before the constructor is called to initialize the instance, the object is still in a semi-initialized state. In this state, the properties of the instance are still the default properties. At this time, if another thread calls the getSingleton() method, it will get this semi-initialized state. The initialized object caused an error.

After the volatile modification is added, reordering is prohibited, which ensures that the singleton is pointed to the allocated memory space after the object is initialized, preventing the occurrence of some uncontrollable errors. Volatile provides a happens-before guarantee, the write to a volatile variable happens-before the subsequent read operation of all other threads.

Principle *
From the above DDL single case use case, in the case of concurrency, the existence of reordering will cause some unknown errors. And after adding volatile, it will prevent reordering, so how does volatile prohibit reordering?

In order to achieve volatile memory semantics, JMM will restrict specific types of compiler and processor reordering, and JMM will formulate a volatile reordering rule table for compilers:

In summary: the
second operation is volatile write, no matter what the first operation is, it will not be reordered. The first operation is volatile read, no matter what the second operation is, it will not be reordered. The first operation is Volatile write, the second operation is volatile read, there will be no reordering

How to ensure that these operations will not send reordering? It is guaranteed by inserting memory barriers. The memory barriers at the JMM level are divided into read (load) barriers and write (Store) barriers. There are four barriers in permutation and combination. For volatile operations, JMM memory barrier insertion strategy:

Insert a StoreStore barrier before each volatile write operation Insert a StoreLoad barrier after each volatile write operation **** Insert a LoadLoad barrier after each volatile read operation Insert a barrier after each volatile read operation LoadStore barrier**

The above barriers are all at the JMM specification level, which means that writing JDK in accordance with this specification can ensure that operations on volatile-modified memory regions will not be sent for reordering.
At the hardware level, a series of memory barriers are also provided to provide consistency. Taking the X86 platform as an example, these memory barrier instructions are mainly provided:

Lfence instruction: the read operation before the lfence instruction must be completed before the read operation after the lfence instruction, similar to the read barrier sfence instruction: the write operation before the sfence instruction must be completed before the write operation after the sfence instruction, similar to Write barrier mfence instruction: The read and write operations before the mfence instruction must be completed before the read and write operations after the mfence instruction, similar to a read and write barrier.

The JMM specification needs to add so many memory barriers, but the actual situation does not need to add so many memory barriers. Take our common X86 processor as an example. The X86 processor will not reorder read-read, read-write, and write-write operations. It will omit the memory barriers corresponding to these three types of operations, and only write- Read operations are reordered. So volatile write-read operations only need to insert StoreLoad barrier after volatile write. In "The JSR-133 Cookbook for Compiler Writers", this is also clearly pointed out:

lock addl 0x0, addl0x0 after (%rsp), (%rsp) is actually a no-op. Add means adding, 0x0 is hexadecimal 0, and rsp is a type of register. Together, it adds 0 to the value of the register. Does adding 0 mean nothing is done? This section of assembly code is just a carrier of the lock command. In fact, as mentioned above, the lock prefix can only be added in front of some special instructions, and add is one of the instructions.
As for why Hotspot uses the lock instruction instead of the mfence instruction, according to my understanding, it saves trouble and is simple to implement. Because the lock function is too powerful, there is no need to consider too much. Moreover, the lock instruction prioritizes the lock cache line. In terms of performance, the lock instruction is not as bad as expected, and the mfence instruction is not as good as expected. Therefore, using lock is a very cost-effective choice. Moreover, lock also has a semantic description of visibility.

Find lock in the instruction list of "IA-32 Architecture Software Developer's Manual":
Insert picture description here

I do not intend to elaborate on the implementation principles and details of the lock instruction here. It is easy to fall into the technical terminology, and it is beyond the scope of this article. If you are interested, you can go to the "IA-32 Architecture Software Developer's Manual".
We only need to know the functions of lock:

Ensure the atomicity of subsequent instruction execution. In Pentium and previous processors, instructions with a lock prefix will lock the bus during execution, making other processors temporarily unable to access memory through the bus. Obviously, this overhead is very high. In the new processor, Intel uses cache locking to ensure the atomicity of instruction execution, and cache locking will greatly reduce the execution overhead of lock prefix instructions. It is forbidden to reorder this instruction with the previous and subsequent read and write instructions. Flush all data in the write buffer to the memory.

In summary, the lock instruction guarantees both visibility and atomicity.

The important thing is to say it again. The lock instruction guarantees both visibility and atomicity. It has nothing to do with any buffer consistency protocol or MESI.

In order not to let you confuse the cache coherency protocol with JMM, in the previous article, I deliberately did not mention the cache coherence protocol, because the two are not a dimensional thing, and the meaning of existence is different. This part , We will talk next time.

Summary The
full text focuses on the visibility and orderliness of volatile. It takes a lot of space to describe some concepts at the bottom of the computer, which may be too boring for readers, but if you can read it carefully, I believe You will gain a little bit more or less.

Without going into further detail, volatile is just a common keyword. In-depth discussion, you will find that volatile is a very important point of knowledge. Volatile can combine software and hardware. If you want to understand it thoroughly, you need to go to the bottom of the computer. But if you do it. Your knowledge of Java will definitely improve further .

Only focusing on the Java language seems very limited. Diverging to other languages, C language, C++ also has the volatile keyword. I haven't read C language, how the volatile keyword in C++ is implemented, but I believe the underlying principles must be the same .

So far this article on the summary of the Java keyword volatile knowledge points is introduced. For more relevant understanding of the Java keyword volatile content, please pay attention to the editor or continue to browse the related articles below. Hope you will support the editor a lot in the future!

Guess you like

Origin blog.csdn.net/dcj19980805/article/details/115349774