Java Multithreading - volatile

Series Chapters

  1. java multithreading - basics
  2. Java Multithreading - Synchronized
  3. java multithreading - wait/notify

sequence

volatileThe keyword is used when the data in the JMM (Java Memory Model) working memory fails to be refreshed to the main memory in time during multi-threaded processing, resulting in abnormal execution of other threads.

loop that can't be exited

 private static boolean RUN = true;
 ​
 // 线程t1中的`while`根据条件变量`RUN`来结束循环
 public static void main(String[] args) {
     Thread t = new Thread(() -> {
         while (RUN) {
             // ...
         }
     }, "t1");
     t.start();
     sleep(1);
     // 线程t1不会如预想的停下来
     RUN = false; 
 }
复制代码

After running the above code, we expected that thread t1 would exit the loop after the condition variable was changed, but it did not. So let's analyze it.

image.png

  1. The value read by the initial t1thread is sent RUNto the working memory. Because the while loop needs to read RUNthe value frequently, it JITwill be optimized and RUNcached in the t1working memory of the thread.
  2. MainThe thread changes the RUNvalue and synchronizes it to the main memory, but the t1thread still reads the value of the working memory, which eventually causes its loop to fail to exit

image.png

volatile solution

volatileCan be used to decorate member variables and static member variables, it makes the thread must go to the main memory to get the value

 // 添加`valatile`关键字
 private static volatile boolean RUN = true;
 ​
 // 这时程序可以正常结束
 public static void main(String[] args) {
     Thread t = new Thread(() -> {
         while (RUN) {
             // ...
         }
     }, "t1");
     t.start();
     sleep(1);
     RUN = false;
 }
复制代码

visibility

The above example reflects the volatilevisibility, which ensures that among multiple threads, volatilethe modification of a variable by one thread is visible to another thread.

Visibility and Atomicity

Atomicity is to ensure that instruction interleaving will not occur in the execution of a certain code block in multiple threads, but visibility can only ensure that the latest value is obtained, and cannot prevent instruction interleaving.

synchronizedThe atomicity of the code block can be guaranteed, and the visibility of variables within the code block can also be guaranteed. But it is a heavyweight operation with relatively low performance.

orderliness

Ordering involves reordering, let's first understand reordering.

Why reorder?

Simple to understand, but very complex at the in-depth level. In-depth see why instruction reordering is required

System level: The CPU accesses the value during calculation. If the existing value in the register is often used, there is no need to go to the memory to read it.

Java level:

 public static void main(String[] args) {
     String a = "a" + "b";// 1
     String b = "ab"; // 2
     System.out.println(a == b);
 }
复制代码

After the above code is optimized by the compiler, it is actually a abstring in the string constant pool. If the reordering optimization is 2executed first , and the string is obtained first ab, then the two strings 1will not be created .ab

Problems caused by reordering

In a single-threaded environment, reordering is not a problem; but in a multithreaded environment, reordering can lead to unexpected results.

 // 计数
 static int i = 1;
 // 定义四个静态变量
 private static int x, y, a, b = 0;
 ​
 public static void main(String[] args) throws InterruptedException {
     while (true) {
         Thread t1 = new Thread(() -> {
             a = 1; // 1
             x = b; // 2
         });
         Thread t2 = new Thread(() -> {
             b = 1; // 3
             y = a; // 4
         });
         t1.start();
         t2.start();
         t1.join();
         t2.join();
         // 打印输出
         String result = "第" + i++ + "次执行x=" + x + ", y=" + y;
         System.out.println(result);
         if (x == 0 && y == 0) {
             break;
         }
         // 修改完后重新赋值
         x = 0;
         y = 0;
         a = 0;
         b = 0;
     }
 }
复制代码

As shown in the above code, after running, we can see that there are several output situations expected:

  1. Synchronous execution, result x=0, y=1
  2. Instructions are interleaved, result x=1, y=1 or x=1, y=0

But there will actually be x=0, y=0, as shown in the figure below (the execution time may be longer)

image.png

This is the result of instruction reordering, as shown in the figure:

image.png

2Before 1execution, 4before 3execution.

volatile principle

volatileThe underlying implementation is the memory barrier mechanism, as follows:

Barrier Type Instructions
LoadLoad Load1; LoadLoad; Load2; Make sure that Load1 reads data before Load2 and all subsequent reads
StoreStore Store1; StoreStore; Store2; Make sure Store1 writes data to main memory, and before Store2 and subsequent writes
LoadStore Load1; LoadStore; Store2; Make sure that Load1 reads data before Store2 and all subsequent writes
StoreLoad Store1; StoreLoad; Load2; Make sure that Store1 is flushed to main memory when writing data, and before Load2 and subsequent reads

The following is the code snippet for processing and instruction in the file under the openjdk8root path , in which a barrier is added after the variable is written ./hotspot/src/share/vm/interpreterbytecodeInterpreter.cppputstaticputfieldvolatileStoreLoad

image.png

Guess you like

Origin juejin.im/post/7079790020495671327