对volatile的理解

保证可见性、不保证原子性、禁止指令重排

1. 保证可见性

当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值

当不添加volatile关键字时示例:

 ```java
   package com.jian8.juc;
   
   import java.util.concurrent.TimeUnit;
   
   /**
    * 1验证volatile的可见性
    * 1.1 如果int num = 0,number变量没有添加volatile关键字修饰
    * 1.2 添加了volatile,可以解决可见性
    */
   public class VolatileDemo {
   
       public static void main(String[] args) {
           visibilityByVolatile();//验证volatile的可见性
       }
   
       /**
        * volatile可以保证可见性,及时通知其他线程,主物理内存的值已经被修改
        */
       public static void visibilityByVolatile() {
           MyData myData = new MyData();
   
           //第一个线程
           new Thread(() -> {
               System.out.println(Thread.currentThread().getName() + "\t come in");
               try {
                   //线程暂停3s
                   TimeUnit.SECONDS.sleep(3);
                   myData.addToSixty();
                   System.out.println(Thread.currentThread().getName() + "\t update value:" + myData.num);
               } catch (Exception e) {
                   // TODO Auto-generated catch block
                   e.printStackTrace();
               }
           }, "thread1").start();
           //第二个线程是main线程
           while (myData.num == 0) {
               //如果myData的num一直为零,main线程一直在这里循环
           }
           System.out.println(Thread.currentThread().getName() + "\t mission is over, num value is " + myData.num);
       }
   }
   
   class MyData {
       //    int num = 0;
       volatile int num = 0;
   
       public void addToSixty() {
           this.num = 60;
       }
   }
   ```
View Code

输出结果:

```java
thread1 come in
thread1 update value:60
//线程进入死循环
```

当我们加上`volatile`关键字后,`volatile int num = 0;`输出结果为:

```java
thread1 come in
thread1 update value:60
main mission is over, num value is 60
//程序没有死循环,结束执行

 

2. ==不保证原子性==

原子性:不可分割、完整性,即某个线程正在做某个具体业务时,中间不可以被加塞或者被分割,需要整体完整,要么同时成功,要么同时失败

验证示例(变量添加volatile关键字,方法不添加synchronized):

 ```java
   package com.jian8.juc;
   
   import java.util.concurrent.TimeUnit;
   import java.util.concurrent.atomic.AtomicInteger;
   
   /**
    * 1验证volatile的可见性
    *  1.1 如果int num = 0,number变量没有添加volatile关键字修饰
    * 1.2 添加了volatile,可以解决可见性
    *
    * 2.验证volatile不保证原子性
    *  2.1 原子性指的是什么
    *      不可分割、完整性,即某个线程正在做某个具体业务时,中间不可以被加塞或者被分割,需要整体完整,要么同时成功,要么同时失败
    */
   public class VolatileDemo {
   
       public static void main(String[] args) {
   //        visibilityByVolatile();//验证volatile的可见性
           atomicByVolatile();//验证volatile不保证原子性
       }
       
       /**
        * volatile可以保证可见性,及时通知其他线程,主物理内存的值已经被修改
        */
       //public static void visibilityByVolatile(){}
       
       /**
        * volatile不保证原子性
        * 以及使用Atomic保证原子性
        */
       public static void atomicByVolatile(){
           MyData myData = new MyData();
           for(int i = 1; i <= 20; i++){
               new Thread(() ->{
                   for(int j = 1; j <= 1000; j++){
                       myData.addSelf();
                       myData.atomicAddSelf();
                   }
               },"Thread "+i).start();
           }
           //等待上面的线程都计算完成后,再用main线程取得最终结果值
           try {
               TimeUnit.SECONDS.sleep(4);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           while (Thread.activeCount()>2){
               Thread.yield();
           }
           System.out.println(Thread.currentThread().getName()+"\t finally num value is "+myData.num);
           System.out.println(Thread.currentThread().getName()+"\t finally atomicnum value is "+myData.atomicInteger);
       }
   }
   
   class MyData {
       //    int num = 0;
       volatile int num = 0;
   
       public void addToSixty() {
           this.num = 60;
       }
   
       public void addSelf(){
           num++;
       }
       
       AtomicInteger atomicInteger = new AtomicInteger();
       public void atomicAddSelf(){
           atomicInteger.getAndIncrement();
       }
   }
   ```

   执行三次结果为:

   ```java
   //1.
   main     finally num value is 19580    
   main     finally atomicnum value is 20000
   //2.
   main     finally num value is 19999
   main     finally atomicnum value is 20000
   //3.
   main     finally num value is 18375
   main     finally atomicnum value is 20000
   //num并没有达到20000
View Code

 


3. 禁止指令重排

有序性:在计算机执行程序时,为了提高性能,编译器和处理器常常会对**==指令做重拍==**,一般分以下三种

```mermaid
graph LR
源代码 --> id1["编译器优化的重排"]
id1 --> id2[指令并行的重排]
id2 --> id3[内存系统的重排]
id3 --> 最终执行的指令
style id1 fill:#ff8000;
style id2 fill:#fab400;
style id3 fill:#ffd557;
```

单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致。

处理器在进行重排顺序是必须要考虑指令之间的**==数据依赖性==**

==多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性时无法确定的,结果无法预测==

重排代码实例:

声明变量:`int a,b,x,y=0`

| 线程1 | 线程2 |
| -------------- | -------------- |
| x = a; | y = b; |
| b = 1; | a = 2; |
| 结 果 | x = 0 y=0 |

如果编译器对这段程序代码执行重排优化后,可能出现如下情况:

| 线程1 | 线程2 |
| -------------- | -------------- |
| b = 1; | a = 2; |
| x= a; | y = b; |
| 结 果 | x = 2 y=1 |

这个结果说明在多线程环境下,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的

volatile实现禁止指令重排,从而避免了多线程环境下程序出现乱序执行的现象

**==内存屏障==**(Memory Barrier)又称内存栅栏,是一个CPU指令,他的作用有两个:

1. 保证特定操作的执行顺序
2. 保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)

由于编译器和处理器都能执行指令重排优化。如果在之零件插入一i奥Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排顺序,也就是说==通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化==。内存屏障另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。

```mermaid
graph TB
subgraph
bbbb["对Volatile变量进行读操作时,<br>回在读操作之前加入一条load屏障指令,<br>从内存中读取共享变量"]
ids6[Volatile]-->red3[LoadLoad屏障]
red3-->id7["禁止下边所有普通读操作<br>和上面的volatile读重排序"]
red3-->red4[LoadStore屏障]
red4-->id9["禁止下边所有普通写操作<br>和上面的volatile读重排序"]
red4-->id8[普通读]
id8-->普通写
end
subgraph
aaaa["对Volatile变量进行写操作时,<br>回在写操作后加入一条store屏障指令,<br>将工作内存中的共享变量值刷新回到主内存"]
id1[普通读]-->id2[普通写]
id2-->red1[StoreStore屏障]
red1-->id3["禁止上面的普通写和<br>下面的volatile写重排序"]
red1-->id4["Volatile写"]
id4-->red2[StoreLoad屏障]
red2-->id5["防止上面的volatile写和<br>下面可能有的volatile读写重排序"]
end
style red1 fill:#ff0000;
style red2 fill:#ff0000;
style red4 fill:#ff0000;
style red3 fill:#ff0000;
style aaaa fill:#ffff00;
style bbbb fill:#ffff00;

 

posted @ 2021-09-22 10:56  天空有朵云  阅读(43)  评论(0)    收藏  举报