Visibility between threads

Visibility at the conceptual level

What is inter-thread visibility?
A thread's modification to the value of a shared variable can be seen by other threads in time.

What are shared variables?
If there are copies of a variable in the working memory of multiple threads, then the variable is a shared variable of these threads.

What is the java memory model? (Java Memory Model, JMM for short) JMM describes the access rules
of various variables (thread shared variables) in java programs , as well as the underlying details of storing and reading variables in and out of memory in the JVM. Rule 1: 1> All variables are stored in the main memory 2> Each thread has its own independent working memory, which holds a copy of the variable used by the thread (a copy of the variable in the main memory) Rule 2 : 1> All operations of threads on shared variables must be performed in their own working memory, and cannot be directly read and written from the main memory 2> Different threads cannot directly access variables in the working memory of other threads, and the transfer of variables between threads It needs to be done through main memory. The model diagram is as follows:







write picture description here

The principle of shared variable visibility implementation:
if thread 1's modification to shared variables is to be seen by thread 2 in time, the following two steps must be followed:
1> Refresh the updated shared variables in working memory 1 to main memory
2> Update the value of the latest shared variable in main memory to working memory 2

There are two types of visibility implementations supported at the java language level:
1>synchronized
2>volatile

synchronized achieves visibility and atomicity

JMM has two regulations on synchronization:
1> Before the thread is unlocked (before exiting the synchronized code block), the latest value of the shared variable must be refreshed to the main memory, that is to say, after the thread exits the value of the synchronized code block, the stored value in the main memory The value of the shared variable is already the latest
2> When the thread locks (after entering the synchronized code block), the value of the shared variable in the working memory will be cleared, so that the latest value needs to be re-read from the main memory when using the shared variable ( Note: Locking and unlocking need to be the same lock)
The combination of the two: the modification of the shared variable before the thread is unlocked will be visible to other threads when it is locked next time.
According to the
above, the process of executing the mutual exclusion code of the thread is introduced: 1> Obtain the mutual exclusion lock (enter the synchronized code block)
2> empty the working memory
3> copy the latest copy of the variable from the main memory to the working memory
4> execute the code
5> flush the value of the changed shared variable to the main memory
6> release the mutex (exit synchronized code block)

What is instruction reordering?
The order of code writing is different from the order of actual execution. Instruction reordering is an optimization made by the compiler or processor to improve program performance (after some codes are translated into machine instructions, if a reordering is performed, the order after reordering may be performed. It is more in line with the characteristics of CPU execution, so that the performance of the CPU can be maximized)
1> Compiler-optimized reordering (compiler)
2> Instruction-level parallel reordering (processor optimization)
3> Memory system reordering (processing optimizer)

What are as-if-serial semantics?
Regardless of the reordering, the results of program execution should be consistent with the results of code-order execution (the java compiler, runtime and processor all guarantee that java follows as-if-serial semantics under single thread)
int num1=1;
int num2= 2;
int sum=num1+num2;
single thread: the order of lines 1 and 2 can be rearranged, but line 3 cannot

Case:

public class SynchronizedDemo {
    //共享变量
    private boolean ready=false;
    private int result=0;
    private int number=1;

    //写操作
    public void write(){
        ready=true; //1.1
        number=2;   //1.2
    }

    //读操作
    public void read(){
        if(ready){  //2.1
            result=number*3;    //2.2
        }
        System.out.println("result的值为:"+result);
    }

    //内部线程类
    private class ReadWriteThread extends Thread{
        //根据构造方法中传入的flag参数,确定线程执行读操作还是写操作
        private boolean flag;
        public ReadWriteThread(boolean flag){
            this.flag=flag;
        }

        @Override
        public void run() {
            if(flag){
                //构造方法中传入true,执行写操作
                write();
            }else{
                //构造方法中传入false,执行读操作
                read();
            }
        }
    }

    public static void main(String[] args) {
        SynchronizedDemo synDemo=new SynchronizedDemo();
        //启动线程执行写操作
        synDemo.new ReadWriteThread(true).start();
        //启动线程执行读操作
        synDemo.new ReadWriteThread(false).start();
    }

}

The execution result of the above code may be as follows:
1> Execution order of the above code: 1.1->2.1->2.2->1.2 The value of result is 3
2> Execution order of the above code: 1.2->2.1->2.2-> 1.1 The value of result is 0.
These four lines of code may be reordered, such as 2.1 and 2.2 after reordering:

int mid=number*3
if(ready){
    result=mid;
}

The reasons why shared variables are not visible between threads:
1> Cross execution of threads
2> Reordering combined with thread cross execution
3> The updated values ​​of shared variables are not updated in time between working memory and main
memory Solution: Just make sure to write The thread finishes executing before the reading thread executes. (Note: Adding the synchronized keyword directly before the read method and the write method will not solve the problem, because the threads will still be executed crosswise, and the read method will be executed first and the write method will be executed first, which will result in inconsistent results.)

public static void main(String[] args) {
        SynchronizedDemo synDemo=new SynchronizedDemo();
        //启动线程执行写操作
        synDemo.new ReadWriteThread(true).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //启动线程执行读操作
        synDemo.new ReadWriteThread(false).start();
    }

volatile implements visibility

Volatile features:
1> The visibility of volatile variables can
be guaranteed 2> The atomicity of compound operations of volatile variables cannot be guaranteed

How to achieve memory visibility?
In-depth: implemented by adding memory barriers and disabling reordering optimization
1> When performing a write operation on a volatile variable, a store barrier instruction will be added after the write operation
2> When performing a read operation on a volatile variable, it will be before the read operation. Add a load barrier instruction In
layman 's terms: every time a volatile variable is accessed by a thread, it forces the value of the variable to be reread from the main memory, and when the variable changes, it forces the thread to refresh the latest value to the main memory. RAM. In this way, different threads can always see the latest value of the variable at any time.

The process of writing a volatile variable by a
thread: 1. Change the value of the copy of the volatile variable in the working memory of the thread
2. Refresh the value of the changed copy from the working memory to the main memory The process of reading a volatile variable by a
thread
: 1. Read from the main memory The latest value of the volatile variable into the thread's working memory
2. Read a copy of the volatile variable from the working memory

Why can't volatile guarantee the atomicity of compound operations on volatile variables?
In layman's terms, volatile cannot be locked, and operations on number++; will be cross-executed by multiple threads, resulting in different results.

private volatile int number=0;//不能保证原子性
number++;   不是原子操作

The above code can be divided into the following operations:
1. Read the value
of number 2. Add 1
to the value of number 3. Write the value of the latest number
Case :

public class VolatileDemo {
    private int number=0;

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public void increase(){
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.number++;
    }

    public static void main(String[] args) {
        VolatileDemo volatileDemo=new VolatileDemo();
        for(int i=0;i<500;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    volatileDemo.increase();
                }
            }).start();
        }
        //如果还有子线程在运行,主线程就让出CPU资源,直到
        //所有的子线程全部运行完了,主线程再继续往下执行
        while (Thread.activeCount()>2){//这里eclipse是1,IDEA是2,不同编译器之间的差别
            Thread.yield();
            System.out.println("我让出了CPU资源,当前还有"+Thread.activeCount()+"个线程");
        }
        System.out.println("number:"+volatileDemo.getNumber());
    }
}

The above code analysis is as follows:
Suppose number=5
1. Thread A reads the value of number, then thread A is blocked, and the number of thread A's working memory is 5
2. Thread B reads the value of number and performs an operation of incrementing by 1
3. Thread B writes the latest number value. At this time, the number of thread B's working memory is 6, and the number of main memory is 6.
4. Thread A regains the CPU execution right and adds 1.
5. Thread A writes The latest number value. At this time, the number of the working memory of thread A is 6, and the number of the main memory is
6. 6. The number++ only increases by 1 twice, so the result may be less than 500

So how to ensure that the result of each execution of the code is 500?
1. Use synchronized code blocks

public void increase(){
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (this){
            this.number++;//加入synchronized,变为原子操作(一次只能被一个线程访问)
        }
 }

2. Use ReentrantLock (similar to synchronized code block, guarantee this.number++; can only be accessed by one thread at a time)

private Lock lock=new ReentrantLock();
    private volatile int number=0;

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public void increase(){
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        lock.lock();//加锁
        try {
            this.number++;
        } finally {
            lock.unlock();//释放锁
        }
    }

Where volatile is applicable
To safely use volatile variables in multithreading, the following must be satisfied:
1. The write operation to the variable does not depend on its current value
Not satisfied : number++, count=count*5
Satisfaction: Boolean variable, which records temperature changes Variable, etc.
2. The variable is not included in an invariant with other variables
Not satisfied invariant low is less than up

Comparison of synchronized and volatile
1. volatile does not require locking, is lighter than synchronized, and does not block threads
2. From the perspective of memory visibility, volatile read operation = enter synchronized code block (lock), volatile write operation = Exit the synchronized code block (unlock)
3.synchronized can guarantee both visibility and atomicity, while volatile can only guarantee visibility, not atomicity

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325604618&siteId=291194637
Recommended