今天会是有offer的一天么-java中volatile关键字的使用

写在前面

volatile关键字在面试中也算是高频问题了,基本上涉及到并发都会被问到这个问题。今天来简单的总结一下。

在这里插入图片描述

先说一下volatile关键字的作用

一. 禁止指令重排

何为禁止指令重排?用一个代码简单解释一下

public class Singleton {
//这里用volatile修饰的目的就是防止指令重排
   private static  volatile Singleton singleton;
   private Singleton(){}
   public static Singleton getSingleton(){
       if(singleton==null){
           synchronized (Singleton.class){
               if(singleton==null){
               //对于这一行代码,其实是分为三个部分执行的
               //首先分配对象的内存空间
               //初始化对象
               //将对象指向刚分配的内存地址
               //如果不用volatile关键字修饰,第二步和第三步可能会调换顺序
               //假如有两个线程同时访问,线程A执行完第一步和第三步还未执行第二步,此时线程B认为sigleton已经初始化完毕,就会带来问题。
                   singleton=new Singleton();
               }
           }
       }
       return  singleton;
   }
}

实现方式

在JVM层面上,volatile内存区的读写都加上了内存屏障,用于实现对内存操作的限制。
在这里插入图片描述

二. 保证“共享变量”的可见性

何为可见性,为什么要保证可见性

首先说明一点,对于单核CPU来讲,是不存在可见性问题的,当然目前的计算机一般都是多核CPU,那么这就会带来可见性问题。例如说我们去执行几行java代码,那么最终这些代码是会交给我们的CPU去执行的,而代码中的一些数据是存放在内存中的。而CPU在去操作这些数据的时候,是不会直接操作内存的,因为每个CPU都有一个自己独立的缓存。例如在对数据进行修改时,首先会将数据从内存装载到缓存;接着在缓存中对数据进行修改;最后将修改后的数据刷新到内存中。
所以对于单核CPU是不存在可见性问题的,但如果是多核就会存在问题。例如我们有两个线程A和B去操作同一变量,线程A交给CPU1去执行,线程B交给CPU2去执行;接着CPU1将数据装载到自己的缓存中进行修改,CPU2将数据装载到自己的缓存中进行修改。最后都将修改后的数据刷新到缓存中。这里面就会出现一个问题,因为每个CPU的缓存都是独立的,CPU1是不知道其CPU2是否已经对数据进行了修改,假如说CPU2将变量由10修改为5,但是CPU2这个时候的缓存中仍然保存的是旧的数据,那么最终就会导致结果出现问题。

volatile 关键字保证可见性的实现方式

在对有volatile关键字修饰的变量进行写操作的时候,汇编后的代码会多出一个LOCK前缀指令,那么这个指令在多核CPU下会引发两件事情:
一. 立刻将当前CPU缓存行中的数据写回到系统内存。(强调一点,如果没有volatile关键字进行修饰,CPU在更新完数据以后是不会立刻将更新后的数据写会到内存中)
二. 这个写会内存的操作会导致其他CPU缓存了该内存地址的数据无效。在多核CPU下,每个CPU都会嗅探在总线上传播过来的数据,来检查自己的缓存是否过期,如果发现缓存行对应的内存地址被修改也就是过期了,就会将当前CPU缓存行设置为无效状态,当需要操作这个数据的时候,会重新从内存中把数据读到缓存行中。

最后说一点volatile关键字是不能保证数据的原子性的。

原创文章 30 获赞 54 访问量 5万+

猜你喜欢

转载自blog.csdn.net/HZGuilty/article/details/105878054