可见性?禁止指令重排?Java中的volatile理解作为程序员你就只懂这点?

本文探讨了计算机中多CPU缓存一致性问题,重点介绍了Intel的MESI协议,以及Volatile关键字在处理多线程指令重排序和内存屏障中的作用。通过实例分析,讲述了双检锁在单例模式中的应用,强调了volatile在轻量级同步中的角色。
摘要由CSDN通过智能技术生成

因为一台计算机有多台CPU,同一个变量,在多个CPU中缓存的值有可能不一样,那么以谁缓存的值为准呢?

既然大家都有自己的值,那么各个CPU间就产生了一种协议,来保证按照一定的规律为准,来确定共享变量的准确值,这样各个CPU在读写共享变量时都按照协议来操作。

这就是缓存一致性协议。

最著名的缓存一致性协议就是Intel的MESI了,说MESI时,先解释一下,缓存行:

缓存行(cache line):CPU高速缓存的中可以分配的最小存储单位,高速缓存中的变量都是存在缓存行中的。

MESI的核心思想就是,当CPU对变量进行写操作时发现,变量是共享变量,那么就会通知其他CPU中将该变量的缓存行设置为无效状态。当其他CPU在操作变量时发现此变量在的缓存行已经无效,那么就会去主内存中重新读取最新的变量。

  • 那么其他CPU是如何发现变量被修改了的呢?

因为CPU和其他部件的进行通信是通过总线来进行的,所以每个CPU通过嗅探总线上的传播数据,来检查自己缓存的值是不是过期了,当处理器发现自己换成行对应的内存地址被修改后,就会将自己工作内存中的缓存行设置成无须状态,当CPU对此变量进行修改时会重新从系统主内存中读取变量。

Volatile的有序性(禁止指令重排)

一般来说,我们写程序的时候,都是要把先代码从上往下写,默认的认为程序是自顶向下顺序执行的,但是CPU为了提高效率,在保证最终结果准确的情况下,是会对指令进行重新排序的。就是说写在前的代码不一定先执行,在后面的也不一定晚执行。

举个例子:

int a = 5; // 代码1
int b = 8; // 代码2
a = a + 4; // 代码3
int c = a + b; // 代码4

上面四行代码的执行顺序有可能是

JMM在是允许指令重排序的,在保证最后结果正确的情况下,处理器可以尽情的发挥,提高执行效率。

当多个线程执行代码的时候重排序的情况就更为突出了,各个CPU为了提高自己的效率,有可能会产生竞争情况,这样就有可能导致最终执行的正确性。

所以为了保证在多个线程下最终执行的正确性,将变量用volatile进行修饰,这样就会达到禁止指令重排序的效果(其实也可以通过加锁,还有一些其他已知规则来实现禁止指令重排序,但是我们这里只讨论volatile的实现方式)。

那么volatile是如何实现指令重排序的呢?

答案是:内存屏障

内存屏障是一组CPU指令,用于实现对内存操作的顺序限制。
Java编译器,会在生成指令系列时,在适当的位置会插入内存屏障来禁止处理器对指令的重新排序。

volatile会在变量写操作的前后加入两个内存屏障,来保证前面的写指令和后面的读指令是有序的。

volatile在变量的读操作后面插入两个指令,禁止后面的读指令和写指令重排序。

有序性,不仅只有volatile能保证,其他的实现方式也能保证,但是如果每一种实现方式都要了解那对于开发人员来说就比较困难了。

所以从JDK5就出现了happen-before原则,也叫先行发生原则。
先行发生原则总结起来就是:如果一个操作A的产生的影响能被另一个操作B观察到,那么可以说,这个操作A先行发生与操作B。

这里所说的影响包括内存中的变量的修改,调用了方法,发送量消息等。

volatile中的先行发生原则是,对一个volatile变量的写操作,先行发生于后面任何地方对这个变量的读操作。

Volatile无法保证原子性

原子性,是指一个操作过程要么都成功,要么都失败,是一个独立的完整的。

就像上面说的,如果多个线程对一个变量进行累加,那么肯定得不到想要的结果,因为累加就不是一个原子操作。

要保证累加最终结果正确,要么对累加变量加锁,要么就用AotomicInteger这样的变量。

/**

  • 双重检查加锁式单例
    */
    public class DoubleCheckLockSingleton implements Serializable{

/**

  • 静态变量,用来存放实例。
    */
    private volatile static DoubleCheckLockSingleton doubleCheckLockSingleton = null;

/**

  • 私有化构造方法,禁止外部创建实例。
    */
    private DoubleCheckLockSingleton(){}

/**

  • 双重检查加锁的方式保证线程安全又能获得到唯一实例
  • @return
    */
    public static DoubleCheckLockSingleton getInstance(){
    //第一次检查实例是否已经存在,不存在则进入代码块
    if(null == doubleCheckLockSingleton){
    synchronized (DoubleCheckLockSingleton.class){
    //第二次检查
    if(null==doubleCheckLockSingleton){
    doubleCheckLockSingleton = new DoubleCheckLockSingleton();
    }
    }
    }

return doubleCheckLockSingleton;
}

}

为什么要进行双重检查呢?
当第一个线程走到第一次检查时发现对象为空,然后进入锁,第二次就检查时也为空,那么就去创建对象,但是这个时候又来了一个线程来到了第一次检查,发现为空,但是这个时候因为锁被占用,所以就只能阻塞等待,然后第一个线程创建对象成功了,由于对象是被volatile修饰的能够立即反馈到其他线程上,所以在第一个线程释放锁之后,第二个线程进入了锁,然后进行第二次检查时,发现对象已经被创建了,那么就不在创建对象了。从而保证的单例。

还有就是如果创建对象,步骤:

  1. 分配内存空间。
  2. 调用构造器,实例化。
  3. 返回内存地址给引用。

如果这三个指令顺序被重排了,那么当多线程来获取对象的时候就会造成对象虽然实例化了,但是没有分配内存空间,会有空指针的风险。
所以加上了volatile的对象,也保证了在第二次检查时不会被已经在创建过程中的对象有被检测为空的风险。

总结一下

volatile其实可以看作是轻量级的synchronized,虽然说volatile不能保证原子性,但是如果在多线程下的操作本身就是原子性操作(例如赋值操作),那么使用volatile会由于synchronized

volatile可以适用于,某个标识flag,一旦被修改了就需要被其他线程立即可见的情况。也可以修饰作为触发器的变量,一旦变量被任何一个线程修改了,就去触发执行某个操作。

volatile的变量写操作happen-before,后面任何对此volatile变量的读操作。

最后

大家看完有什么不懂的可以在下方留言讨论.
谢谢你的观看。
觉得文章对你有帮助的话记得关注我点个赞支持一下!

作者:纪莫
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

这个月马上就又要过去了,还在找工作的小伙伴要做好准备了,小编整理了大厂java程序员面试涉及到的绝大部分面试题及答案,希望能帮助到大家

在这里插入图片描述

在这里插入图片描述

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
图片转存中…(img-PrJAfeTa-1713581215365)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 18
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值