Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Memory Model in Java concurrency

2025-03-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

This article mainly explains the "memory model in Java concurrency". The content of the explanation is simple and clear, and it is easy to learn and understand. Please follow the editor's train of thought to study and learn the memory model in Java concurrency.

CPU and memory

Before I talk about JMM, I'd like to talk to you about hardware. We should all know that CPU, which performs computing operations, does not have the storage capacity. It is only responsible for doing corresponding operations on the transmitted data according to instructions, while the task of data storage is handed over to memory to complete. Although the running speed of memory is much faster than that of hard disk, it is still too slow compared with 3GHz CPU or even 5GHZ CPU. In CPU's eyes, memory runs like the younger brother of a younger brother. When memory is read and written once, CPU can think about hundreds of lives: grin:. But CPU's computing power is a shortage of resources, ah, can not be so wasted, so we have to find a way to solve this problem.

There is no problem that cannot be solved by a cache. If so, add another cache-- Lu Xun: anyway, I didn't say that.

So people came up with the idea of adding a cache to CPU (why adding a cache rather than increasing the speed of memory involves the hardware cost). For example, the I99900k CPU of Intel used by bloggers has a three-tier cache of up to 16m, so the memory model on the hardware is probably shown in the following figure.

As you can clearly see in the figure, one or more layers of cache are built within CPU, and L1 Cache is unique to the core in CPU, and different Core cannot be shared, while L2 Cache is shared by all cores. To put it simply, when CPU reads a data, it will first read it at the nearest Cache level, and if it cannot find it, it will go to the next level to look for it. If it cannot find it, it will load it from memory, and if it can get the required data in Cache, it will not read it in memory. When writing the data back, it will also be written to the Cache and wait for the right time to write it to memory (one of the details is the issue of the cache line, which is at the end of the article). Because there are multiple cache levels and some cache cannot be shared, there is a problem of memory visibility.

To give a simple example: suppose there are two Core, CoreA and CoreB, and they both have their own L1Chace and shared L2Cache. At the same time, there is a variable X with a value of 1, which has been loaded on L2Cahce. At this time, CoreA needs to use the variable X to perform the operation, first go to L1Cache to find, miss, continue to search in L2Cache, hit successfully, load X into L1Cahce, and then modify X to 2 and write to L1Cache after a series of operations. At the same time, CoreB also needs X to do the operation. At this time, he goes to his own L1Cahce search, misses, continues the L2Cache search, hits successfully, and loads Xray 1 into his own L1Cache. At this point, there is a problem. CoreA has obviously changed the value of X to 2, but CoreB still reads XForm1, which is the problem of memory visibility.

Seeing that the friends here may have to ask, what is the situation with you, blogger? you have gradually forgotten the title. You have agreed on the Java memory model. Why are you talking about so many hardware problems? (╯ '□') ╯ the ┻━┻

Main memory and working memory in Java

Don't worry, guys. In fact, JMM is very similar to the model on the hardware level above. Take a look at the picture below.

How? does it look like it? it can be simply understood that the working memory of the thread is the L1Cahce exclusive to Core in the CPU, and the main memory is the shared L2Cache. So the above memory consistency problem will also exist in JMM, and JMM needs to make some column rules to ensure memory consistency, which is also a difficult problem of Java multithreading concurrency, so what rules does JMM make?

The inter-memory interaction is first of all the interaction protocol between the working memory of the main memory, specifically defining the following operations (and ensuring that these operations are atomic):

Lock (lock) A variable that acts on main memory, identifying a variable as a thread exclusive state.

Unlock (unlock) acts on a variable in main memory, releasing a variable in a locked state, which can be locked by other threads only after release.

Read (read) A variable that acts on the main memory to transfer the value of a variable from the main memory to the thread's working memory for later load operations.

Load (load) is a variable that acts on working memory. It puts the value of the variable obtained by the read operation from main memory into a copy of the variable in working memory.

Use (uses) a variable that acts on working memory, passing a variable value in working memory to the execution engine, which is performed whenever the virtual machine encounters a bytecode instruction that requires the value of the variable.

Assign (assignment) acts on a variable in working memory. It assigns a value received from the execution engine to the variable in working memory, and performs this operation whenever the virtual machine encounters a bytecode instruction to assign the variable.

Store (stores) variables that act on working memory, passing the value of a variable in working memory to main memory for subsequent write operations.

Write (write) acts on a variable in main memory, passing store operations from the value of a variable in working memory to a variable in main memory.

At the same time, it also stipulates that the following rules must be followed when performing the above eight operations:

If you want to copy a variable from the main memory to the working memory, you need to perform the read and load operations as directed, and if you synchronize the variables from the working memory back to the main memory, you need to perform the store and write operations sequentially. However, the Java memory model only requires that the above operations must be performed sequentially, but there is no guarantee that they must be performed continuously.

One of the read and load, store and write operations is not allowed to appear alone

A thread is not allowed to discard its most recent assign operation, that is, variables must be synchronized to main memory after being changed in working memory.

A thread is not allowed to synchronize data from working memory back to main memory for no reason (no assign operation has occurred).

A new variable can only be born in main memory, and it is not allowed to use an uninitialized variable (load or assign) directly in working memory. That is, assign and load operations must be performed before use and store operations are performed on a variable.

A variable allows only one thread to perform lock operations on it at a time, but the lock operation can be executed repeatedly by the same thread. After executing lock many times, the variable will be unlocked only if the unlock operation is performed the same number of times. Lock and unlock must appear in pairs

If a lock operation is performed on a variable, the value of the variable will be emptied in working memory, and the value of the variable will need to be initialized by a load or assign operation before the execution engine can use it

If a variable is not locked by a lock operation in advance, it is not allowed to perform a unlock operation on it, nor is it allowed to unlock a variable that is locked by another thread.

Before you can unlock a variable, you must first synchronize the variable to main memory (perform store and write operations).

(the above section refers to and references the contents of "in-depth understanding of the Java Virtual Machine")

Volatile (ensures memory visibility and forbids instruction reordering)

JMM has some special rules for volatile-modified variables.

Memory visibility

To put it simply, the volatile keyword means that there is a volatile-decorated variable x, which is read directly from main memory when a thread needs to use it, and is written directly to main memory when a thread modifies the value of the variable. From the previous analysis, we can conclude that volatile with these characteristics can ensure the memory visibility and memory consistency of a variable.

Instruction reordering

Instruction reordering is a common operation in most CPU, and there will be instruction reordering in JVM at run time. A simple example: chestnut:

Private void test () {int a, b, e, b, e, b, b, e, e, b, b, e, e, B, G, B, G, G, B, G, B, C, B, G, C, B, C, B, G, B, G, G

Suppose there is such a method above, and there are these four lines of code inside. Then JVM may reorder it, while instruction reordering stipulates that no matter how much as-if-serial reorders (compiler and processor to improve parallelism), the execution result of (single-threaded) programs cannot be changed. According to this rule, compilers and processors do not reorder instructions that have dependencies, but instructions that do not have dependencies may be reordered. In the above example, there is a dependency between the first line of code and the 4th line of code, so the instructions of the first line of code must come before 2Jing 3Jing 4, because it is impossible to assign a value to an undefined variable. There is no interdependence between lines 2 and 3, so instruction reordering may occur here, executing 3 before 2. The last fourth line of code is dependent on the previous three lines of code, so it must be executed at the end.

Since JVM specifically points out that instruction reordering has the same effect as unordered instructions only in single thread, does it mean that there will be some problems in multithreading? The answer is yes, multi-threaded instruction reordering will bring some unexpected results.

Int astat0; / / flag, as an identifier, identifies whether to write boolean flag= false; public void writer () {aqq10 / Accord 1 flag=true;//2} public void reader () {if (flag) System.out.println ("a:" + a);}

Suppose there is a class that has the field and method of the above part, which is designed to use flag as a sign of whether the write is complete or not, which is not a problem in a single thread. At this time, two threads execute the writer and reader methods respectively, regardless of memory visibility for the time being. Assuming that the writes to an and flag are immediately known by other threads, what do you think is the value of output an at this time? ten?

Even if memory visibility is not taken into account, the value of a may still output 0, which is the problem with instruction reordering. There is no dependency on the code at comments 1 and 2 in the above code, there is no problem with executing 1 or 2 in a single thread, and instruction reordering may occur at this time according to as-if-serial principles.

The volatile keyword can disable instruction reordering.

The problem with long,double

We all know that the operations between the eight main memory and working memory defined by JMM are atomic, but there are some exceptions for long and double, two 64-bit data types.

Allows the virtual machine to divide the read and write operations of 64 data of long and double that are not modified by volatile into two 32-bit read and write operations, that is, the virtual machine is not required to guarantee the atomicity of their load and store,read,write operations. But most virtual machine implementations guarantee the atomicity of these four operations, so most of the time we don't need to use volatile decorations on long,double objects.

Performance problem

Volatile is the lightest operation provided by Java to ensure the visibility of memory, which is much faster than the heavyweight synchronized, but how much faster it can be. What we can know is that the performance consumption of volatile-modified variable reads is almost the same as that of normal variables, while write operations are slower. So when volatile can solve our problems (memory visibility and disable instruction reordering), we should give priority to using volatile over locks.

Memory semantics of synchronized

The simple summary is that

When the program enters the synchronized block, it clears the variables used in the synchronized block from the working memory, so that it will retrieve them from the main memory when the variables need to be accessed. When the program exits the synchronized block, the changes to the congratulations variables in the block are flushed to the main memory. Such reliance on synchronized also ensures memory visibility.

Memory semantics of final

Final also ensures the visibility of memory.

Once the final-decorated field is initialized in the constructor and the constructor does not pass the this reference, then the value of the final field can be seen in other threads.

Postscript CPU cache line and pseudo-sharing what is pseudo-sharing

From the previous article, we know that there is a Cache between CPU and Memory, while Cache is stored internally by row, and rows have a fixed size, which are called cache lines. When a variable accessed by CPU is not in Cache, it is fetched in memory and the data of a cache row size of the memory where the variable is located is read into Cache. Because a read is not a single object but an entire cache line, multiple variables may be read into a cache line. A cache line can only be operated by one thread at the same time, so when multiple threads modify multiple variables in a cache line at the same time, it will cause other threads to wait and cause performance loss (but in the case of a single thread, pseudo-sharing will improve performance, because multiple variables may be cached at once, saving reading time for subsequent variables).

How to avoid pseudo-sharing

After Java8, you can use the @ sun.misc.Contended annotation provided by JDK to solve pseudo-sharing, such as the threadLocalRandom field in Thread.

Thank you for your reading, the above is the content of "memory model in Java concurrency". After the study of this article, I believe you have a deeper understanding of the memory model in Java concurrency, and the specific use needs to be verified in practice. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report