Volatile和CAS(可见性、有序性、内存屏障、MESI)解析
2021-03-02 16:10:07 0 举报
AI智能生成
Volatile+CAS(可见性/有序性/内存屏障/MESI)
作者其他创作
大纲/内容
阅读导航
<b style=""><font color="#0076b3">线程 👉</font></b>
<b><font color="#0076b3">CPU+JMM</font></b> 👉
<b><font color="#f15a23">CAS+Volatile</font></b>
<b><font color="#0076b3">Synchronized</font></b> 👉
<b><font color="#0076b3">JUC</font></b> 👉
<b><font color="#0076b3">线程池</font></b> 👉
<b style="font-size: inherit;"><font color="#0076b3">ThreadLocal</font></b><span style="font-size: inherit;"> 👉</span><br>
CAS
CAS是一种自旋操作,有3个操作数,变量v,期待值a,修改值b,当a=v时,将变量值修改为b,否则什么都不做
<b>Compare and Swap:比较并交换</b>
底层原理是sum.misc.Unsafe类,调用native方法实现<br>在系统的最底层CPU指令支持:lock cmpxchg
cmpxchg:CPU级别的CAS操作,非原子性
lock:多核CPU时,采用类似总线锁来完成CAS
lock指令在执行后面指令的时候锁定一个北桥信号
缺点
ABA问题
使用时间戳或版本号机制
只能保证一个共享变量的原子操作
如果CAS一直不成功会给CPU带来很大开销
应用:Atomic包
volatile
<b>可见性</b>
主要解决线程间共享变量的可见性和有序性
线程在对volatile修饰的变量 <b>执行写操作时会立刻把写入的值刷新到主内存,同时使副本失效</b>
① happen-before
对于两个操作 A 和 B,这两个操作可以在不同的线程中执行。如果 A Happens-Before B<br>那么可以保证,当 A 操作执行完后,A 操作的执行结果对 B 操作是可见的(8种规则之一)
原理:执行写操作的时候JVM会给CPU发送一条 <b>lock前缀指令</b>
CPU会立即将这个值写回主内存(<font color="#0076b3"><i><u>总线锁</u></i></font>)
同时使其他CPU缓存中的副本失效(触发MESI)
② MESI:缓存一致性协议
原理:各个CPU会对总线进行嗅探,如果发现有人修改了某个缓存的数据,那么CPU就会<br>将自己本地的缓存过期掉,再次读取那个变量的时候,就会从主内存重新加载最新的数据
MESI是四种修饰缓存行的四种状态的缩写,详细分析见上一小节(CPU架构)👉
<b>有序性</b>
内存屏障<br><b>禁止指令重排</b>
内存屏障其实也是一种JVM指令,Java内存模型的重排规则会要求Java编译器在生成<br>JVM指令时插入特定的内存屏障指令,通过这些内存屏障指令来禁止特定的指令重排序
四种类型
LoadLoad:保证load1的读取操作在load2及后续读取操作之前执行
LoadStore:在stroe2及其后的写操作执行前,保证load1的读操作已读取结束
StoreLoad:保证store1的写操作已刷新到主内存之后,load2及其后的读操作才能执行
StoreStore:在store2及其后的写操作执行前,保证store1的写操作已刷新到主内存
<b>不保证原子性</b>
如果一个变量被volatile修饰了,那么肯定可以保证每次读取这个变量值的时候得到的值是<br>最新的,但是一旦需要对变量进行自增这样的非原子操作,就不会保证这个变量的原子性
解决
加锁
AtomicInteger原子类
应用
单例模式的懒汉式(double-check)
适合只有一个线程修改,其他线程读取的情况
比如作为状态位,A线程调用了某个方法让B线程感知
关于作者
<b style=""><font color="#b296c7">我的博客</font></b> 👉
<b><font color="#00a650">微信公众号</font></b> 👉
<b><font color="#f15a23">GitHub 导航</font></b> 👉
<b><font color="#662c90">ProcessOn 主页 👉</font></b>
0 条评论
下一页