The story of concurrent programming - shared model of memory

Shared model memory


1. JVM memory abstract model

The main thing is to abstract the cache, memory , and disk under the CPU into main memory and working memory, which is
reflected in visibility , atomicity, and
order

2. Visibility


If the t thread reads a static variable frequently, the JIT compiler will store it in the thread's cache, so even if the main thread modifies the static variable in the main memory, it will have no effect, because the t thread reads It's in the cache. Therefore, the program judgment is still an error and cannot be stopped.

@Slf4j(topic = "c.Test32")
public class Test32 {
    
    
    // 易变
    static boolean run = true;

    public static void main(String[] args) throws InterruptedException {
    
    
        Thread t = new Thread(()->{
    
    
            while(true){
    
    
                    if(!run) {
    
    
                        break;
                    }
            }
        });
        t.start();

        sleep(1);
            run = false; // 线程t不会如预想的停下来
    }
}

Insert image description here
The solution
volatile and synchronized can prevent threads from accessing the cache, and must access the run in the main memory.

@Slf4j(topic = "c.Test32")
public class Test32 {
    
    
    // 易变
     static boolean run = true;
     static Object lock=new Object();

    public static void main(String[] args) throws InterruptedException {
    
    
        Thread t = new Thread(()->{
    
    
            while(true){
    
    
                synchronized (lock){
    
    
                    if(!run) {
    
    
                        break;
                    }
                }

            }
        });
        t.start();

        sleep(1);
        synchronized (lock){
    
    
            run = false; 
        }

    }
}

Adding soout can also solve visualization problems. The reason is that this println is a synchronize method, that is, if it is to be output, it will be in the synchronization block. The synchronization block can complete the visualization, and then the natural run can be read.

public void println(boolean x) {
    
    
        synchronized (this) {
    
    
            print(x);
            newLine();
        }
    }

3. Instruction reordering

Why order reordering?
Because each statement is composed of multiple instructions, which is equivalent to multiple divisions of labor. Some of these divisions of labor can be completed at the same time, so they are combined first. Other instructions that require the results of the previous step are queued and waited behind.

Weird result
Here is actually the instruction rearrangement will cause the result to be 0. In fact, the thread 2 instruction rearrangement executes ready=true first, and then it is switched to thread 1. It just happens to make calculations through if first, and finally switches to thread 2 to execute num=2 to solve the problem. Using volatile can prevent variables in
Insert image description here
front
of Code reordering
Insert image description here
volatile principle
The principle of volatile is actually a memory barrier. The write barrier is to synchronize all the variables before the modified variable to the main memory and modify them in the main memory every time, and prevent the previous code instructions from being rearranged behind the barrier. If it is a read barrier, then all variables below the volatile variable are synchronized to the main memory to prevent the code below the barrier from being rearranged before the barrier, thus protecting the volatile attribute.
It ensures that the variables of the write barrier are up-to-date,
but it cannot solve the problem of instruction interleaving, that is, the order of instructions can only be guaranteed in the local thread, but the problem of multi-thread instruction interleaving cannot be guaranteed. In order to prevent multiple locks in
Insert image description here
Insert image description here
Insert image description here
the double-check
singleton mode, you can After judging empty first, then lock, and then judge empty. The advantage of this is that after the object is created, it only needs to be judged to be empty, and there is no need to lock it again. Only the first time you need to create an object with a lock to prevent multiple threads from creating objects at the same time.
Question
The first if code will be rearranged by the instruction, why is it rearranged?
Insert image description here
The key to the root cause analysis of the double check (dcl)
is that if (INSTANCE==null) is a code outside the monitor, then the problem is that when INSTANCE=new Singleton() is executed, it is not an atomic operation. Including invokespecial to execute the constructor instruction and putstatic to assign a value to the reference (find the heap memory address of the object)
Supplement: So why does synchronize still have instruction rearrangement here?
The reason is that it will cause instruction rearrangement, but it will not cause atomization, visualization and ordering problems in synchronize, but here is the reason why there are two threads and synchronize does not fully control the variable INSTANCE.
Insert image description here
Insert image description here
Solution
You can use the volatile read-write barrier to prevent code instructions from being rearranged outside the barrier, so that you can avoid invokespecial from going after putstatic.
Insert image description here
Insert image description here
happen-before (visibility)
synchronize
volatile
wait for the thread to finish executing before reading the variable.
After the static variable is written, the thread calls it.
The modification before the thread interrupts
the default value of the variable.
Insert image description here
Insert image description here
Exercise
balking exercise
instruction reordering problem, the solution can be used to synchronize Frame these variables to prevent other threads from passing the first if when switching, resulting in repeated execution problems
Insert image description here
Insert image description here
1. The
reason why final is added is to prevent the class from being inherited, and then the rewritten method will bring the singleton object to be changed
2 , How to prevent deserialization from destroying the singleton?
You need to add a method that returns Obj and directly return the singleton object instead of recreating it through bytecode. 3. Why is the
structure privatized
to prevent it from being created many times
? 4. Can initialization ensure thread safety?
Static variables are initialized when the class is loaded.
5. The reason for not making the Instance public
is to prevent direct modification, provide encapsulation, and hide details.

Insert image description here
1. All the bytecodes are public final static class objects, so the instance objects can be restricted.
2. There will be no concurrency problems. The static variables have been loaded when the class is loaded.
3. The singleton will not be destroyed by reflection, enum 4.
It will not be destroyed by deserialization. It implements serialization and returns a singleton method.
5. It is a hungry Chinese style.
Insert image description here
Insert image description here
Summary
Visibility (jvm optimizes speed, puts variables into thread cache)
in order Sexuality (instruction rearrangement, optimized execution speed)
whether the happen-before write is visible to the thread
volatile principle
Synchronous mode balking


Guess you like

Origin blog.csdn.net/weixin_45841848/article/details/132614338