volatile关键字

133 阅读4分钟

1.原理

为了提高处理器的执行速度,在处理器和内存之间增加了多级缓存来提升。但是由于引入了多级缓存,就存在缓存数据不一致问题。

但是,对于volatile变量,当对volatile变量进行写操作的时候,JVM会向处理器发送一条lock前缀的指令,将这个缓存中的变量回写到系统主存中。

但是就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题,所以在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议

缓存一致性协议:每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。

所以,如果一个变量被volatile所修饰的话,在每次数据变化之后,其值都会被强制刷入主存。而其他处理器的缓存由于遵守了缓存一致性协议,也会把这个变量的值从主存加载到自己的缓存中。这就保证了一个volatile在并发编程中,其值在多个缓存中是可见的

2.三大特性

a.可见性

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。所以,就可能出现线程1改了某个变量的值,但是线程2不可见的情况。

验证可见性 我们先不使用volatile关键字 用多个线程访问一个整型变量

image.png 我们可以发现主线程获取到的都是0所以一直在循环,而线程a已经更改了变量的值为60,但是没有通知到主线程,导致主线程一直是0,这就说明,这是没有可见性的

这个时候我们用volatile修饰这个变量,再执行

image.png

可以发现线程a改变的值 通知到主线程了 实现了变量的可见性

b.不保证原子性

线程是CPU调度的基本单位。CPU有时间片的概念,会根据不同的调度算法进行线程调度。当一个线程获得时间片之后开始执行,在时间片耗尽之后,就会失去CPU使用权。所以在多线程场景下,由于时间片在线程间轮换,就会发生原子性问题。

为了保证原子性,需要通过字节码指令monitorenter和monitorexit,但是volatile和这两个指令之间是没有任何关系的。

所以,volatile是不能保证原子性的。 image.png 以上代码比较简单,就是创建20个线程,然后分别执行1000次i++操作。正常情况下,程序的输出结果应该是20000,但是,多次执行的结果都小于20000。这其实就是volatile无法满足原子性的原因。

为什么会出现这种情况呢,那就是因为虽然volatile可以保证inc在多个线程之间的可见性。但是无法inc++的原子性。

c.禁止指令排序

什么是指令重排

简单的来说,我们写一句i++ 但是底层执行的时候会分成几步,如果是单线程是没有指令重排问题的,但是多线程就不一样了

image.png 例如:我有如下代码 执行顺序就有可能是 1234 2134 1324

image.png

如果多个线程访问 就会有如下问题

image.png

所以多线程下静止指令重排,这是必须的

volatile原理如下

image.png

3.volalite与单例模式

掘金链接如下:juejin.cn/post/685457…