Understanding of the three major features of JMM and simple use of volatile

JMM concept

What are JMMs?
The full name of JMM Java Memory Model, namely Java memory model, is a logical abstract concept, just a set of conventions or specifications.
Thinking: JMM存在的意义是什么?或者说它的作用是什么?
It stipulates that in any operating system, reading and manipulating data between Java threads must follow this process, showing the abstract relationship between threads and main memory, so that Java programs can achieve consistent access effects on all platforms.

Each thread takes data from the main memory and copies it to its own working memory (cache) for operation. If it is a write operation, the modified data in its own working memory is written into the memory after the operation. That is to say, all data operations in the working memory of each thread are invisible until it is written to the main memory.
insert image description here
The overall process is as follows:
all variables defined in the code that can be shared by multiple threads are stored in the main memory, and each thread has its own independent memory space (in the cache). When a thread wants to operate a certain data , you must first copy a copy of this data from the main memory to your own workspace, and the prototype data is still in memory. After the cache data in its own space is processed by the CPU, it is written from the cache to the memory. Note: The CPU cannot directly read and write data from the memory ( no leapfrog ). The workspace cached by each thread is not visible.

Thinking: 为什么不直接操作内存?
Because there is a gap between the speed of the CPU and the speed of the memory, if all the data is read from the memory, most of the processing time of the CPU is wasted on reading the data. At this time, a cache is introduced into the composition of the computer. The cache is much faster than the memory. When operating data, in order to improve the speed, first read the data from the memory to the cache, and then read the data in the cache to In the CPU, this can reduce the speed difference between the CPU and the memory. The data in the memory is visible to each thread, and the data read by the current thread from the memory to the cache is only visible to the current thread.

insert image description here
Thinking: Since the content of each work area in a thread is invisible to each other, how to communicate between threads. For example: thread A changes the variable num from 0 to 1, and thread B still reads the copy of 0, how does B know that A has changed num to 1?

Shared memory that can be seen by each thread. Thread A writes num into the shared memory immediately after modifying num, while thread B does not read from the cache when operating the variable num, but reads the latest data from the memory to the cache. Doesn't this make the B thread not use the cache content when reading the variable num, but read it from the memory again without slowing down the speed?
To achieve immediate communication between threads for certain data operations, it is necessary to target these data In this way, if it is not sensitive to the timeliness of communication, you can also use the cache to improve the speed.

Thinking: What is the physical hardware corresponding to the logical shared memory and the independent working space of the thread?
Shared memory is the unit of G on the computer 内存条, and most of the CPU cache refers to the faster speed, but the capacity is small, and the unit is M or K.寄存器

Three characteristics of JMM

The three major characteristics of JMM are three issues that should be considered in concurrent programming.

  • Atomicity
    is different from atomicity in MySQL transactions. In MySQL, a section is regarded as a whole, either all succeed or all fail. In JMM, it focuses on an atomic operation, and no other threads can enter the operation. 更像是隔离性.
    A line of code in our code may be compiled into multiple assembly instructions by the JVM and then executed. For example: two threads A and B modify num=0 in the shared memory to 1 in their respective thread spaces, assuming process 1: Copy num from the shared memory into a copy to the memory space Process 2: Copy num in the memory space Modify the value of num to 1, process 3: put the modified num into the main memory.
    Assume that thread A starts first and executes 1 and 2. At this time, if there is no atomicity, thread A is suspended by the operating system or thread B breaks in (the suspension is determined by the operating system, and the timing cannot be intervened by itself), and thread B will be in one breath All processes 1, 2, and 3 are executed. At this time, the value of the shared memory num is already 1. At this time, A is awakened, and its process 3 is executed, which is equivalent to rewriting num=1 again. Since threads do not have atomicity when assigning values ​​to variables (I think it is more like isolation), other threads intrude in the section of reading data, manipulating data, and writing data back, which eventually causes two threads to operate num together, and finally makes num=1, not 2. Therefore, the atomicity of JMM should be considered in a multi-threaded environment.

  • Visibility
    Multiple threads have the same variable in their own memory space. If any thread modifies this variable, all other threads will immediately perceive it.
    Because of JMM, each thread first copies a variable into its own memory space, but the memory space of each thread is independent and there is no sharing. If one of the threads modifies this variable, other threads will not perceive it until the memory space of this thread is refreshed to the memory, and they see the data that was originally copied from the main memory.

  • Orderliness
    Orderliness is due to the fact that the JVM optimizes our own code and adjusts the order of some codes so that it can achieve faster results at runtime. Just like during an exam, there are questions arranged in order in the test paper, and we usually choose to do what we know, which is more efficient. The order of the questions in the test paper is like the order in which we write the code, and the order of the actual questions is like the order in which the code is compiled. Even the rearrangement is not arbitrary rearrangement, it must have dependencies in front. For example: x=1, y=x; y depends on x, then x=1 will definitely be in front of y=x even after rearrangement.
    It is a good thing to optimize the order and ensure the final consistency of the results, but there may be some problems in concurrent programming. The
    following are examples of different results if instruction rearrangement occurs:

public class Application {
    
    
    public static void main(String[] args) throws CloneNotSupportedException, InterruptedException {
    
    
        ReSortDemo reSortDemo = new ReSortDemo();
        new Thread(()->{
    
    reSortDemo.t1();},"A").start();
        new Thread(()->{
    
    reSortDemo.t2();},"B").start();
    }
}
class ReSortDemo{
    
    
    int num=0;
    boolean flag=false;
    void t1(){
    
    
        num=999; //语句1
        Thread.sleep(100);
        flag=false;  // 语句2
    }
    void t2(){
    
    
        while (!flag){
    
    
        }
        num=100;
        System.out.println(num);
    }

}

If thread A starts first, according to the execution order of statement 1 and statement 2, when statement 2 is executed, thread B enters the method, and the final calculation result num=100; but note: there is no dependency between statement 1 and statement 2, and it may happen The instructions are rearranged so that statement 2 is in front of statement 1, then the final calculation result is num=999; but if it is in a single-threaded environment, this problem will not occur.

The use of Volatile

Volatile is a keyword that can provide 轻量级a synchronization mechanism. Adding it in front of the variable can ensure the visibility of the variable during multi-threaded computing (any thread modifying its own workspace will make other threads immediately visible) and prohibit instruction rearrangement ( The marked part will not be optimized by the compiler). However Volatile不保证原子性, this is different from synchronized、lockthis heavy-duty lock.

Visibility of Volatile :

Verify the invisibility between multiple threads in JMM:
idea: prepare two threads (one is A thread, the other is main thread) and a resource class. After the A thread starts, copy the data from the resource class and modify the data, but make sure that the main thread has copied the original data to its own workspace before modifying the data (otherwise the A thread may write the data into the main memory very quickly , will make the main thread copy out the modified data). After A finishes modifying the data, it is judging whether the data fetched by the mian thread has changed. By default, JMM will be invisible, the following is the verification of the code:
insert image description here

Thinking: Why does thread A sleep for a while before operating data?
In order to ensure that when the main thread gets the data, it must be that A has not been modified. Only in this way can the JMM model be verified. If the A thread is modified and put into the main memory, the mian thread will go to the memory to copy, so that the thread has completed the data copy through the main memory, and the data invisibility cannot be verified.

Verify the visibility of Volatile:
insert image description here
Thinking: What is the underlying process like?
Since the volatile keyword is added to the num variable, after thread A copies the data from the main memory to its own workspace and modifies the data, it will be written into the main memory immediately. When the main thread acquires this variable, it does not read old data from its own workspace, but reads the latest data from the main memory to its own space interval. Although the working areas between threads are invisible to each other, threads can communicate instantly through main memory.
Thinking: Does the performance decrease compared to not adding Volatile?
After adding Volatile, the threads need to frequently obtain the latest data from the main memory, and the workspace cache for this variable is invalid. The use of the work area is to improve the speed, so it will reduce the performance.

Thinking: If Volatile is not used, can it be solved by synchronized?
The idea of ​​using synchronized to solve variable visibility: If multiple threads operate on the same variable, synchronized object locks are added to the method of modifying data and the method of obtaining data. In this way, when thread A operates num, it grabs the object lock of this resource class. The main thread has no object lock of resource class and cannot call the getNum() method. The main thread can only acquire the lock and call the getNum() method after the thread A has finished modifying and releasing the lock (the variable has been written from the workspace to the main memory when the lock is released). visibility.
insert image description here
But using synchronized to lock in the method makes the thread execute serially again too cumbersome.

Verify that volatile does not achieve atomicity

  1. The idea of ​​​​not adding Volatile
    : setting multiple threads to operate the same variable, if the atomicity is not guaranteed, value overwriting may occur.
    insert image description here

Thinking: 是什么原因造成的?
It is because num is not atomic in the process of fetching, assigning, and putting the value back. There are other thread processes entering between fetching and writing back the value. For example: Thread A has just finished fetching the value, thread B enters, executes in one go, and A continues to execute, resulting in the same value and the same operation to get the same result. The effect of addition is not achieved, but a value override of the same value occurs.
Thinking: countDownLatch在这里的作用是什么?
Because the final result is output in the main thread, what may happen is that the main thread prints the result before each thread has finished calculating, making the result smaller. After adding countDownLatch, specify the number of threads during initialization, and each thread will be reduced by 1 at the end. If it is not reduced to 0, the main thread will wait. Output the result until all new cities are executed.
2. Add Volatile
insert image description here

Thinking: 如何理解这里的原子性?
Although there is only one line of code for variable assignment, there may be multiple lines of code at compile time, which makes the JVM have multiple steps during execution. If these steps cannot be completed in one go, and other threads break in, the atomicity inside this variable will be destroyed. Atomicity is to treat a section as a whole and indivisible.

Thinking: volatile的可见性在这里发挥怎样的作用?
It can ensure that after a thread modifies num, other threads can immediately get the latest modified value when reading. Others, but during the write operation, the data in the previous memory is copied to the data in your own workspace, which cannot be read, and may not be the latest data when writing. Therefore, the visibility of volatile has no effect on atomicity.
Thinking: 那如果数据在写的时候,再次进行读取并与预期值比较,比较符合预期值后再进行修改(判断与修改保持原子性)这样是否就能达到整体的原子性?
This is CASthe bottom principle. Still in the above example, thread A changes the value of num to 1 and then hangs up before writing in the future, and the value of num is still 0 at this time. The B thread breaks in, reads the value of num as 0, modifies num to 1, and then writes it again. At this time, num=1, at this time, thread A obtains the execution right, and thread A still thinks that num should be 0 at this time, but it is 1 at this time. If thread A makes an atomic judgment again, it will read that num is already 1, which is inconsistent with expectations. Then do other processing. Similar to an optimistic lock, it solves the atomicity problem in multi-threading.
Thinking: How to modify the above code to be atomic?

  1. Locks can be used to change to serial execution when writing num. The atomicity problem is only when multi-threaded writing is performed. If it is directly changed to serial when writing, the problem will be solved directly.
    The method of using synchronized locks:
    insert image description here
    the method of using locks:
    insert image description here
  2. Using CAS
    insert image description here
    CAS is more like an optimistic lock, while synchronized is more like a pessimistic lock. In contrast, using CAS is more efficient.

Guess you like

Origin blog.csdn.net/m0_52889702/article/details/128868906