Volatile, synchronized visibility, order, atomic code proof (basic hard core)

table of Contents

0. Introduction

1. Visibility

1.1 Invisibility

1.2 volatile visibility

1.3 synchronized visibility

2. Atomicity

2.1 Atomicity

2.2 volatile non-atomicity

2.3 synchronized atomicity

3. Orderliness

3.1 Orderliness

3.2 volatile orderliness

3.3 Synchronized order

4. Programmer's learning method experience

5. Summary


0. Introduction


The previous article "Synchronized Usage Principles and Lock Optimization and Upgrade Process" analyzed the principle of synchronized keywords in detail from the perspective of interviews. This article mainly focuses on the visibility, atomicity, and orderliness of volatile keywords using code analysis. Synchronized also assists Prove it to deepen your understanding of locks.

image.png

 

1. Visibility


1.1 Invisibility

After thread A manipulates the shared variable, the shared variable is invisible to thread B. Let's look at the following code.

package com.duyang.thread.basic.volatiletest;
/**
 * @author :jiaolian
 * @date :Created in 2020-12-22 10:10
 * @description:不可见性测试
 * @modified By:
 * 公众号:叫练
 */
public class VolatileTest {

    private static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(() -> {
            while (flag){
                //注意在这里不能有输出
            };
            System.out.println("threadA over");
        });
        threadA.start();
        //休眠100毫秒,让线程A先执行
        Thread.sleep(100);
        //主线程设置共享变量flag等于false
        flag = false;
    }
}

In the above code, thread A is started in the main thread, and the main thread sleeps for 100 milliseconds. The purpose is to let thread A execute first. The main thread finally sets the shared variable flag to false. The console does not output the result, and the program loop is not over. As shown in the figure below, after the main thread executes with flag = false, the Java memory model (JMM), the main thread sets the flag value of its working memory to false and then synchronizes to the main memory. At this time, the main memory flag = false, and thread A does not The latest flag value (false) of the main memory is read , the main thread is executed, and the working memory of thread A has been occupying the cpu time slice. The latest flag value will not be updated from the main memory. Thread A cannot see the latest value of the main memory. The value used by the thread is inconsistent with the value used by the main thread, leading to program confusion. This is the invisibility between threads, so you should be able to understand. The invisibility between threads is the root cause of the infinite loop of the program.

image.png

1.2 volatile visibility

In the above case, we have used code to prove that the shared variables between threads are invisible. In fact, you can draw the conclusion from the above figure: as long as the working memory of thread A can sense the value of the shared variable flag in the main memory, it’s fine. , So that the latest value can be updated to the working memory of thread A, as long as you can think of this, the problem is over, yes, the volatile keyword realizes this function, thread A can perceive the main memory shared variable The flag has changed, so it is forced to read the latest value of flag from the main memory and set it to its own working memory. Therefore, if you want the VolatileTest code program to end normally, use the volatile keyword to modify the shared variable flag, private volatile static boolean flag = true ; you're done . The hardware foundation of volatile bottom layer implementation is based on hardware architecture and cache coherency protocol. If you want to dive deeper, you can read the previous article " What is Visibility?" (Easy to understand) ". You have to try it to get a reward!

image.png

1.3 synchronized visibility

Synchronized is to ensure that shared variables are visible. Each time the lock is acquired, the latest shared variable is read from the main memory again.

/**
 * @author :jiaolian
 * @date :Created in 2020-12-22 10:10
 * @description:不可见性测试
 * @modified By:
 * 公众号:叫练
 */
public class VolatileTest {

    private static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(() -> {
            while (flag){
                synchronized (VolatileTest.class){
                    
                }
            };
            System.out.println("threadA over");
        });
        threadA.start();
        //休眠100毫秒,让线程A先执行
        Thread.sleep(100);
        //主线程设置共享变量flag等于false
        flag = false;
    }
}

In the above code, I added a synchronized code block to the while loop of thread A, and synchronized (VolatileTest.class) locks the class of the VolatileTest class. Finally, the program outputs "threadA over" and the program ends. It can be concluded that thread A reads the latest data of the main memory shared variable flag=false each time before locking. This proves that the synchronized keyword and volatile have the same visibility semantics.

image.png

2. Atomicity


2.1 Atomicity

Atomicity means that an operation either succeeds or fails, and it is an indivisible whole.

2.2 volatile non-atomicity

/**
 * @author :jiaolian
 * @date :Created in 2020-12-22 11:22
 * @description:Volatile关键字原子性测试
 * @modified By:
 * 公众号:叫练
 */
public class VolatileAtomicTest {

    private volatile static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        Task task = new Task();
        Thread threadA = new Thread(task);
        Thread threadB = new Thread(task);
        threadA.start();
        threadB.start();
        //主线程等待AB执行完毕!
        threadA.join();
        threadB.join();
        System.out.println("累加count="+count);
    }

    private static class Task implements Runnable {
        @Override
        public void run() {
            for(int i=0; i<10000; i++) {
                count++;
            }
        }
    }

}

In the above code, threads A and B are started in the main thread, and each thread adds 10,000 times to the shared variable count. After thread AB runs, the accumulated value of count is output; the following figure is the console output result, the answer is not equal to 20,000. It proves that volatile-modified shared variables do not guarantee atomicity . The root cause of this problem is count++. This operation is not an atomic operation. In the JVM, count++ is divided into three operations.

  • Read the count value.
  • Increase count by 1.
  • Write count value to main memory.

When count++ is operated in multiple threads, thread safety issues arise.

image.png

2.3 synchronized atomicity

We use the synchronized keyword to transform the above code.

/**
 * @author :jiaolian
 * @date :Created in 2020-12-22 11:22
 * @description:Volatile关键字原子性测试
 * @modified By:
 * 公众号:叫练
 */
public class VolatileAtomicTest {

    private static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        Task task = new Task();
        Thread threadA = new Thread(task);
        Thread threadB = new Thread(task);
        threadA.start();
        threadB.start();
        //主线程等待AB执行完毕!
        threadA.join();
        threadB.join();
        System.out.println("累加count="+count);
    }

    private static class Task implements Runnable {
        @Override
        public void run() {
            //this锁住的是Task对象实例,也就是task
            synchronized (this) {
                for(int i=0; i<10000; i++) {
                    count++;
                }
            }
        }
    }
}

In the above code, the synchronized (this) synchronization code block is added to the thread increment method. This locks the Task object instance, which is the task object; the execution order of threads A and B is synchronized, so the AB thread runs finally The result is 20000, and the console output result is shown in the figure below.

image.png

3. Orderliness


3.1 Orderliness

What is order? The Java program code we write is not always executed in order, and program reordering (instruction rearrangement) may occur. The advantage of doing this is to let the program code of the execution block execute first, and the slow-executing program is placed in Going later, improve the overall operating efficiency. After drawing a simple diagram, give a practical application case code, you will learn it.

image.png

As shown in the above figure, task 1 takes a long time and task 2 takes a short time. After the JIT compiles the program, task 2 is executed first, and then task 1 is executed. It has no effect on the final running result of the program, but it improves efficiency (task 2 runs first) It has no effect on the result, but it improves the response speed)!

/**
 * @author :jiaolian
 * @date :Created in 2020-12-22 15:09
 * @description:指令重排测试
 * @modified By:
 * 公众号:叫练
 */
public class CodeOrderTest {
    private static int x,y,a,b=0;
    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {

        while (true) {
            //初始化4个变量
            x = 0;
            y = 0;
            a = 0;
            b = 0;
            Thread threadA = new Thread(new Runnable() {
                @Override
                public void run() {
                    a = 3;
                    x = b;
                }
            });
            Thread threadB = new Thread(new Runnable() {
                @Override
                public void run() {
                    b = 3;
                    y = a;
                }
            });
            threadA.start();
            threadB.start();
            threadA.join();
            threadB.join();
            count++;
            if (x == 0 && y==0) {
                System.out.println("执行次数:"+count);
                break;
            } else {
                System.out.println("执行次数:"+count+","+"x:"+x +" y:"+y);
            }
        }

    }
}

In the above code, threads A and B are started in a loop. If x and y are both equal to 0, the program exits. count is the program count counter. The following figure is a partial result of the console program printing. It can be analyzed from the figure that when x and y are both equal to 0, a = 3; x = b; two lines of code are reordered, and b = 3; y = a; two lines of code are also done Reorder. This is the result of the reordering of the optimized code by the JIT compiler.

image.png

3.2 volatile orderliness

Shared variables modified by volatile are equivalent to barriers. The role of barriers is to not allow instructions to be rearranged at will. Orderliness is mainly manifested in the following three aspects.

3.2.1 The instructions on the barrier can be reordered.

/**
 * @author :jiaolian
 * @date :Created in 2020-12-22 15:09
 * @description:指令重排测试
 * @modified By:
 * 公众号:叫练
 */
public class VolatileCodeOrderTest {
    private static int x,y,a,b=0;
    private static volatile int c = 0;
    private static volatile int d = 0;
    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {

        while (true) {
            //初始化4个变量
            x = 0;
            y = 0;
            a = 0;
            b = 0;
            c = 0;
            d = 0;
            Thread threadA = new Thread(new Runnable() {
                @Override
                public void run() {
                    a = 3;
                    x = b;
                    c = 4;
                }
            });
            Thread threadB = new Thread(new Runnable() {
                @Override
                public void run() {
                    b = 3;
                    y = a;
                    d = 4;
                }
            });
            threadA.start();
            threadB.start();
            threadA.join();
            threadB.join();
            count++;
            if (x == 0 && y==0) {
                System.out.println("执行次数:"+count);
                break;
            } else {
                System.out.println("执行次数:"+count+","+"x:"+x +" y:"+y);
            }
        }

    }
}

In the above code, threads A and B are started in a loop. If x and y are both equal to 0, the program exits. Shared variables c and d are volatile modified, which is equivalent to a memory barrier, and count is a program counter. The following figure is a partial result of the console program printing. It can be analyzed from the figure that when x and y are both equal to 0, a = 3; x = b; two lines of code are reordered, and b = 3; y = a; two lines of code are also done Reorder. It proves that the instructions on the barrier can be reordered.

image.png

3.2.2 The instructions below the barrier can be reordered.

image.png

As shown in the figure above, put the c and d barriers on the ordinary variables, and execute the code again, there will still be cases where x and y are equal to 0 at the same time, which proves that the instructions under the barrier can be rearranged.

3.2.3 The instructions above and below the barrier cannot be reordered.

/**
 * @author :jiaolian
 * @date :Created in 2020-12-22 15:09
 * @description:指令重排测试
 * @modified By:
 * 公众号:叫练
 */
public class VolatileCodeOrderTest {
    private static int x,y,a,b=0;
    private static volatile int c = 0;
    private static volatile int d = 0;
    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {

        while (true) {
            //初始化4个变量
            x = 0;
            y = 0;
            a = 0;
            b = 0;
            c = 0;
            d = 0;
            Thread threadA = new Thread(new Runnable() {
                @Override
                public void run() {
                    a = 3;
                    //禁止上下重排
                    c = 4;
                    x = b;
                }
            });
            Thread threadB = new Thread(new Runnable() {
                @Override
                public void run() {
                    b = 3;
                    //禁止上下重排
                    d = 4;
                    y = a;
                }
            });
            threadA.start();
            threadB.start();
            threadA.join();
            threadB.join();
            count++;
            if (x == 0 && y==0) {
                System.out.println("执行次数:"+count);
                break;
            } else {
                System.out.println("执行次数:"+count+","+"x:"+x +" y:"+y);
            }
        }

    }
}

As in the above code, putting the barrier in the middle will prohibit the rearrangement of upper and lower instructions. The x and y variables cannot be 0 at the same time. The program will always fall into an infinite loop and cannot end, which proves that the code above and below the barrier cannot be rearranged.

3.3 Synchronized order

/**
 * @author :jiaolian
 * @date :Created in 2020-12-22 15:09
 * @description:指令重排测试
 * @modified By:
 * 公众号:叫练
 */
public class VolatileCodeOrderTest {
    private static int x,y,a,b=0;
    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {

        while (true) {
            //初始化4个变量
            x = 0;
            y = 0;
            a = 0;
            b = 0;
            Thread threadA = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (VolatileCodeOrderTest.class) {
                        a = 3;
                        x = b;
                    }
                }
            });
            Thread threadB = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (VolatileCodeOrderTest.class) {
                        b = 3;
                        y = a;
                    }
                }
            });
            threadA.start();
            threadB.start();
            threadA.join();
            threadB.join();
            count++;
            if (x == 0 && y==0) {
                System.out.println("执行次数:"+count);
                break;
            } else {
                System.out.println("执行次数:"+count+","+"x:"+x +" y:"+y);
            }
        }
    }
}

In the above code, x and y cannot be equal to 0 at the same time. The class object of the VolatileCodeOrderTest of the synchronized lock. Threads A and B are the same lock. The code is executed synchronously and has a sequential order, so synchronized can also ensure order. Sex. It is worth noting that the above code synchronized cannot use synchronized (this). This means that the current thread is threadA or threadB, which is not the same lock. If you use this to test, x and y will be equal to 0 at the same time.

4. Programmer's learning method experience


You can see that I spent a lot of energy on analyzing multi-threading in recent articles, talking about visibility, atomicity and other issues, because these features are the basis for understanding multi-threading. In my opinion, the foundation is particularly important, so how do I repeat it? I think it’s not too much to write. Before this, many newbies or children with 2 to 3 years of work experience often asked me about Java learning methods . My advice to them is to have a solid foundation and don’t come to learn advanced knowledge. Or frameworks, such as ReentrantLock source code, springboot framework, just like you play a game, you play the higher level of difficulty at the beginning, once the slope is higher, you will be more uncomfortable and strenuous, let alone facing the book, this is the real slave The process of getting started to giving up. At the same time, don’t just think about it when you’re studying, think you’ll pass this knowledge point by yourself. This is not enough. You need to write more code and practice more. You can deepen your understanding and memory of knowledge in the process. In fact, there is You seem to understand a lot of knowledge, but you haven't put it into practice, and you haven't really understood it . I don't recommend the method that you can't do it. I have been working for 7 years after graduating from my undergraduate degree and have been engaged in the front-line research and development of Java. , I also brought the team in the middle, because I have also walked through many detours and walked through the pits, and I still have a certain experience of the learning process. I will continue to organize and share some experience and knowledge in the days to come To everyone, I hope everyone likes to follow me. I'm called practicing, just start practicing when I call a slogan!

To sum it up, there are two sentences: do more and have a solid foundation .

image.png

5. Summary


Today I talked to you about the 3 important features of multithreading. I explained the meaning of these terms in detail in the way of code implementation. If you execute the code carefully, you should be able to understand it. If you like, please like and pay attention. My name is Lian [ Official Account ] , I call and practice.

image.png

Guess you like

Origin blog.csdn.net/duyabc/article/details/111561857