volatile feature code verification

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.

 

Tags: Java

Posted by XeNoMoRpH1030 on Tue, 24 May 2022 01:21:19 +0300