Java并发编程-volatile关键字使用及原理

文章目录

  • volatile简介
  • Java的内存模型JMM以及共享变量的可见性
  • volatile变量的特性
  • volatile不适用的场景
  • volatile原理

看这篇博客之前大家可以看一下另外一篇博客,这样就会对为什么需要volatile,和充分的理解我这篇博文有帮助!
可见性/原子性/有序性

volatile简介

volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差(有时它更简单并且开销更低),而且其使用也更容易出错。

Java的内存模型JMM以及共享变量的可见性

JMM决定一个线程对共享变量的写入何时对另一个线程可见(可见性),JMM定义了线程和主内存之间的抽象关系;共享变量存储在主内存中,每个线程都有一个自己的本地内存。私有本地内存保存了线程需要的主内存中数据的拷贝,线程对于数据的读写操作必须在自己的工作内存中而不能再主内存中进行。
Java并发编程-volatile关键字使用及原理_第1张图片
这里假如线程A从主内存中读取了数据并在私有本地内存做了修改,但是还没来得及写会主内存,这个时间线程B读取主内存就是未修改的数值,这里就牵扯到线程安全问题!牵扯到线程安全问题这是我们想解决,首要最安全的就是使用java的同步技术加锁(synchronized或者Lock)但是这些技术是重量级的对于性能消耗太大,所以就可以考虑使用另外一种技术volatile。

volatile变量的特性

  • 保证线程的可见性,但不保证原子性(不完全满足线程安全三大特性)
    当线程修改一个被volatile变量时,这是会将本地内存中的数据刷新到主内存中去!
    上面的这个写操作会导致,其他线程的本地内存中的数据刷新成主内中的数据。
  • 禁止指令重排序(读写屏障)
    重排序是指编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段。重排序需要遵守一定规则:
    a.重排序操作不会对存在数据依赖关系的操作进行重排序。

比如:a=1;b=a; 这个指令序列,由于第二个操作依赖于第一个操作,所以在编译时和处理器运

行时这两个操作不会被重排序。

b.重排序是为了优化性能,但是不管怎么重排序,单线程下程序的执行结果不能被改变

比如:a=1;b=2;c=a+b这三个操作,第一步(a=1)和第二步(b=2)由于不存在数据依赖关系, 所以可能会发

生重排序,但是c=a+b这个操作是不会被重排序的,因为需要保证最终的结果一定是c=a+b=3。

重排序在单线程下一定能保证结果的正确性,但是在多线程环境下,可能发生重排序,影响结果,下例中的1和2由于不存在数据依赖关系,则有可能会被重排序,先执行status=true再执行a=2。而此时线程B会顺利到达4处,而线程A中a=2这个操作还未被执行,所以b=a+1的结果也有可能依然等于2。
使用volatile关键字修饰共享变量便可以禁止这种重排序。若用volatile修饰共享变量,在编译时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序,volatile禁止指令重排序也有一些规则:

a.当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后
面的操作可见;在其后面的操作肯定还没有进行;
b.在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放
到其前面执行。

class Test_two{
       int num=1;
       volatile boolean ready=false;

     public void actor1(){//t1线程执行
         if (ready){
             int b=num+num;
         }else {
             this.num=3;
         }
     }
     public void actor2(){//t2线程执行
         num=0;
         ready=true;
     }
     
}

上面这段代码如果ready没有加volatile关键字,那么可能当t2线程执行actor2时先执行ready=true;后执行num=0;在ready=true还没执行num=0;时t1线程执行if (ready)那么就会使得最后b=1+1=2。但是加了volatile boolean ready=false;那么就不会将 num=0;
ready=true;两句重排序因为有读写屏障(当然给ready加了volatile,num可加可不加)。

volatile不适用的场景

(1)volatile不适合复合操作
也就是非原子操作,这时需要java的同步技术synchronized或者Lock)或者java并发包中的原子操作类,原子操作类是通过CAS循环的方式来保证其原子性的

volatile原理

volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用“内存屏障”来实现的。观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

I. 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内
存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
II. 它会强制将对缓存的修改操作立即写入主存;
III. 如果是写操作,它会导致其他CPU中对应的缓存行无效。

你可能感兴趣的:(#,并发编程-Java)