1. visibility
If the value of the variable modified by the volatile keyword changes, other threads will get it immediately, thus avoiding the situation of dirty reading.
1 public class TestVolatile { 2 3 public static void main(String[] args) { 4 MyData myData = new MyData(); 5 new Thread(new Runnable() { 6 @Override 7 public void run() { 8 System.out.println("Enter the operation data thread"); 9 try { 10 Thread.sleep(1000); 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 //call method assignment 15 myData.changeData(); 16 System.out.println(Thread.currentThread().getName()+" : "+myData.data); 17 } 18 },"operation data thread").start(); 19 20 // The main thread checks whether the data has changed 21 while (myData.data == 0){ 22 23 } 24 System.out.println("main thread ends"); 25 } 26 } 27 28 class MyData{ 29 int data = 0; 30 public void changeData(){ 31 this.data = 2020; 32 } 33 34 }
As in the above code, there are two threads operating the MyDdata data class, look at the execution results
It can be seen from the results that the main thread has never obtained the data update information, and the data storage in the memory can be visually viewed with the graph
The memory thread of the main thread does not get the data update.
The effect of adding volatile to the following variables
1 public class TestVolatile { 2 3 public static void main(String[] args) { 4 MyData myData = new MyData(); 5 new Thread(new Runnable() { 6 @Override 7 public void run() { 8 System.out.println("Enter the operation data thread"); 9 try { 10 Thread.sleep(1000); 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 //call method assignment 15 myData.changeData(); 16 System.out.println(Thread.currentThread().getName()+" : "+myData.data); 17 } 18 },"operation data thread").start(); 19 20 // The main thread checks whether the data has changed 21 while (myData.data == 0){ 22 23 } 24 System.out.println("main thread ends"); 25 } 26 } 27 28 class MyData{ 29 volatile int data = 0; 30 public void changeData(){ 31 this.data = 2020; 32 } 33 34 }
Take a look at the execution result
It is found that the main method has obtained the data update. Thus verifying the visibility of volatile.
2. There is no guarantee of atomicity
code directly
1 public class TestVolatile1 { 2 3 public static void main(String[] args) { 4 MyData1 myData = new MyData1(); 5 6 new Thread(new Runnable() { 7 @Override 8 public void run() { 9 //call method assignment 10 myData.changeData(); 11 for(int i = 0;i < 9999;i++) { 12 myData.changeData(); 13 } 14 System.out.println(Thread.currentThread().getName()+" : "+myData.data); 15 } 16 },"thread 1").start(); 17 18 19 new Thread(new Runnable() { 20 @Override 21 public void run() { 22 //call method assignment 23 myData.changeData(); 24 for(int i = 0;i < 9999;i++) { 25 myData.changeData(); 26 } 27 System.out.println(Thread.currentThread().getName()+" : "+myData.data); 28 } 29 },"thread 2").start(); 30 31 new Thread(new Runnable() { 32 @Override 33 public void run() { 34 //call method assignment 35 myData.changeData(); 36 for(int i = 0;i < 9999;i++) { 37 myData.changeData(); 38 } 39 System.out.println(Thread.currentThread().getName()+" : "+myData.data); 40 } 41 },"thread 3").start(); 42 43 new Thread(new Runnable() { 44 @Override 45 public void run() { 46 //call method assignment 47 myData.changeData(); 48 for(int i = 0;i < 9999;i++) { 49 myData.changeData(); 50 } 51 System.out.println(Thread.currentThread().getName()+" : "+myData.data); 52 } 53 },"thread 4").start(); 54 55 while (Thread.activeCount() > 1) { 56 Thread.yield(); 57 } 58 59 System.out.println("Final value:"+myData.data); 60 } 61 } 62 63 class MyData1{ 64 volatile int data = 0; 65 public void changeData(){ 66 data++; 67 } 68 69 }
We can predict that if it is normal, the final data we should get should be 40000, but the result is as follows
It can be seen that the final data is not the result we want. Multiple threads operate volatile modified variables at the same time, and the atomicity of the data cannot be guaranteed.
So how to solve this problem, use sychornized, it can be handled, but this is a heavyweight lock, not recommended, you can also use AtomicInteger to handle this situation The implementation code is as follows
1 import java.util.concurrent.atomic.AtomicInteger; 2 3 public class TestVolatile1 { 4 5 public static void main(String[] args) { 6 MyData1 myData = new MyData1(); 7 8 new Thread(new Runnable() { 9 @Override 10 public void run() { 11 //call method assignment 12 myData.changeData(); 13 for(int i = 0;i < 9999;i++) { 14 myData.changeData(); 15 } 16 System.out.println(Thread.currentThread().getName()+" : "+myData.data); 17 } 18 },"thread 1").start(); 19 20 21 new Thread(new Runnable() { 22 @Override 23 public void run() { 24 //call method assignment 25 myData.changeData(); 26 for(int i = 0;i < 9999;i++) { 27 myData.changeData(); 28 } 29 System.out.println(Thread.currentThread().getName()+" : "+myData.data); 30 } 31 },"thread 2").start(); 32 33 new Thread(new Runnable() { 34 @Override 35 public void run() { 36 //call method assignment 37 myData.changeData(); 38 for(int i = 0;i < 9999;i++) { 39 myData.changeData(); 40 } 41 System.out.println(Thread.currentThread().getName()+" : "+myData.data); 42 } 43 },"thread 3").start(); 44 45 new Thread(new Runnable() { 46 @Override 47 public void run() { 48 //call method assignment 49 myData.changeData(); 50 for(int i = 0;i < 9999;i++) { 51 myData.changeData(); 52 } 53 System.out.println(Thread.currentThread().getName()+" : "+myData.data); 54 } 55 },"thread 4").start(); 56 57 while (Thread.activeCount() > 1) { 58 Thread.yield(); 59 } 60 61 System.out.println("Final value:"+myData.data); 62 } 63 } 64 65 class MyData1{ 66 AtomicInteger data = new AtomicInteger(); 67 public void changeData(){ 68 data.getAndIncrement(); 69 } 70 71 }
The execution result is as follows
In this way, the problem of data atomicity is solved.
3. Instruction rearrangement
The order of instructions is reordered when the JVM is compiling Java code, or when the CPU is executing JVM bytecode. Under the premise of not changing the execution result of the program, optimize the running efficiency of the program (do not change the execution result of the program under a single thread)
look at a simple code
1 public class Data { 2 3 int a = 1; //step 1 4 int b = 2; //Step 2 5 int c = a+b; //Step 3 6 7 }
Under a single thread, the code executes the result c of result 3, but it is not necessarily the execution order of 1, 2, and 3 during the execution process. After the instruction rearrangement occurs, it may be 2, 1, and 3. The single thread has no effect on the project.
But if it is multi-threaded, there will be problems. Check out the method below
1 public class Volatile { 2 3 int a = 1; 4 boolean flag = false; 5 6 public void dosome1() { 7 a = 2;// step 1 8 flag = true; //Step 2 9 } 10 11 public void dosome2() { 12 if(flag){ 13 int b = a+a; // Step 3 14 } 15 } 16 }
Step 3 of the above code is actually two steps. For better understanding, it can be regarded as one step.
If thread A operates dosome1 and thread B operates dosome2 If no instruction reordering occurs
The possible order may be 1,2,3 b=4 , which is what we expect,
The following sequence will also appear
1,3,2 3,1,2 These two possibilities, if it is these two, it means that the conditions are not met, and the b variable is not declared.
However, if the rearrangement occurs, because there is no dependency between 1 and 2, it is very likely that instruction rearrangement will occur, and the result of the execution may appear in the following order
2,3,1 If this order occurs, the variable b will be declared, and the result will be 2; this result will be terrifying, just like we have done a project, and the result of each execution cannot be determined. This must not be possible. To solve this problem, we can use volatile to modify variables. Of course sychornized can also be solved.
Rearrangement is a troublesome process, which is a simple understanding, and will be discussed in detail later.