前言
Volatile的能够保证并发编程的两大特性
一是可以保证可见性,即一个线程对共享变量的修改,能够对其他线程立即可见
二是可以保证有序性,即防止操作系统通过指令重排序来优化程序,毕竟在高并发的场景下,要保证重排序后的执行结果跟排序前的一致,是非常难的
正文
本文将围绕Volatile的三个内来展开
- 内存模型(JMM)
- 内存语义
- 内存屏障
1. 内存模型(JMM)
关于L1、L2、L3高速缓存,L1和L2是一个CPU一个,L3是整个计算机共享一个,所以下图可能画的不太准确(懒得重画了),不过不影响各位看官食用哈
- 由于路途遥远,所以各国需要通过使者L1、使团L2(可能还会有L3),来跟王国公主联系
- 王国CPU1和王国CPU2各自为政,互不往来,使者只会跟使团联系,不会彼此联系,信息严重滞后
- 如果CPU1的王子迎娶了公主A,过几个月后,又传来CPU2的王子迎娶公主B的消息,那么公主可能会被判重婚罪
看不懂的小伙伴,建议先去百度一下Java内存模型,这里只是以一个不太恰当的比喻来形容一下,毕竟学习嘛,总得自娱自乐一下,要不然可太苦了
由于内存寸土寸金,禁不起这么折腾,所以有了各王国间签订了一个MESI协议,这样各国的王子就能实时知道公主的婚姻情况了
MESI协议,又称缓存一致性协议,用于管理多个 CPU cache 之间数据的一致性。
关于MESI协议,大佬的这篇从CPU缓存看缓存的套路,已经讲得非常清楚了,建议多看几遍
2. 内存语义
这个嘛,也就是字面上的意思了,简单的理解呢,就是遇到volatile,读写都会走主内存,缓存暂时就失效了
经过编译器,到了汇编层面,我们可以看到,volatile修饰的变量,会加上一个Lock前缀指令,而这个Lock前缀指令会锁缓存(替代锁总线)。关于总线锁和缓存锁,这里就不展开了,感兴趣的小伙伴自行百度哈~(等我以后有空了再补充吧)
3. 内存屏障
Volatile使用硬件层的内存屏障(分为两种Load Barrier 和 Store Barrier即读屏障和写屏障),来阻止屏障两侧的指令重排序
下面这段关于读写屏障的描述,来自知乎用户@随风而逝对关于内存屏障的几个问题?的回答,会比其他答案好理解点
读写屏障包括全能型屏障都属于指令。读屏障作用于Invalidate queue,每次cpu遇到这个指令都将自己积压已久的invalidate ack处理掉,具体就是使得对应的缓存失效,这样自己再读的时候,能保证读到最新的副本。写屏障作用于store buffer,将处于store buffer中的写操作真正执行掉,具体就是向其他CPU发送invalidate cache 的消息,写自己的独占缓存。全能型屏障这两件事都做。
谈到指令重排序,就不得不提happens-before规则,happens-before规则定义一些禁止编译优化的场景
静下心来耐心看,光看文字,相信大家还是能够看懂的,就是面试吹水的时候,记不太住,毕竟这是定好的规则
小小总结一下
大佬们的劳动成果(排名不分先后)
MESI协议
从CPU缓存看缓存的套路
面试官最爱的volatile关键字
带你了解缓存一致性协议 MESI
结语
Volatile对于并发编程来说,总归是绕不开的,平时也看了很多零散的知识,今天也算是稍微整理了一下,相信对以后的复习和面试能够有所帮助。
最后还是那句,本人所有博客不做商业用途,如若涉及侵权,还请联系删除,互相学习,一起进步