硬件层
缓存锁:
MESI协议
缓存中数据4种状态
Modified CPU自己修改过
Exclusive 独占
Shared 共享的
Invaild 其他CPU修改过
但是有些无法被缓存的数据或者跨域多个缓存行的数据 依旧需要使用总线锁
缓存行
cpu读取缓存的时候以缓存行CacheLine为基本单位,目前大部分实现为64个字节 MESI 标记的数据是整个缓存行
比如当2个int互相不相关 位于同一缓存行 被两个不同cpu锁定 产生互相影响产生伪共享问题
解决办法 可以参照disruptor中环形指针 前后补数据 来确保被执行的数据在单独缓存行中
public long p1, p2, p3, p4, p5, p6, p7; // cache line padding
private volatile long cursor = INITIAL_CURSOR_VALUE;
public long p8, p9, p10, p11, p12, p13, p14; // cache line padding
处理器乱序问题
CPU缓存结构
L3是cpu共享的缓存区
L2和L1是每一个CPU自己内部的缓存区
CPU为了提高效率 在确保指令无依赖关系的情况下,会在一条指令执行过程中 执行下面指令 比如
合并写操作 写1 写2 写3 然后同时往l1缓存区再写
读操作 读1 读2 是不冲突的就可能会是先读2 再读1
如何保证有序
内存屏障
X86 CPU原语内存屏障指令 屏障两侧的指令不可以重排
sfence: 在sfence指令前 写的操作 必须在sfence指令后的写操作之前完成
lfence: 在lfence指令前的读操作 必须在lfence之后的读操作之前完成
mfence: 在mfence指令前的读写操作 必须在mfence指令后的读写操作之前完成,保证系统在后面的memory访问之前,先前的memory访问都已经结束
Lock 指令
在执行指令的时候 读取到的内存 不能被其他cpu改变
Java中JMM模型实现
三大原则
可见性
CPU1 写入的数据 对CPU2是可见的
也就是CPU2能知道 对应的数据被CPU1修改过 需要去主内存去从新读取对应新的数据
原子性
在CPU1修改某个数据的时候 其他CPU不准许修改对应的值 比如锁的机制
顺序性
CPU在执行一段语句的时候 可能会优化对应指令数据 比如非依赖关系的指令顺序可能会被重排序执行
通过缓存锁机制或者内存屏障的方式保证执行顺序不会被打乱
主存储区(内存)
主内存区 指的是多个CPU都可以访问的一个公共内存区,CPU读取数据的时候会有从主存储区读取到独占内存去也就是工作内存区中,读取的是主存储的一个副本数据
工作存储区(内存)
CPU在执行指令的时候 会将主存储区的数据读取到自己对应的工作存储区,然后进行读写运算等操作
内存屏障
理解内存屏障的方式有很多,我一般理解为,给CPU一个信号,告诉CPU两侧数据不能被打乱顺序执行哪怕是非依赖关系的指令
LoadLoad
双读屏障
load1 //读操作
LoadLoad //读屏障
load2 //读操作
确保load1在加载数据之前 load2以及后续的load 不准许加载数据
必须要等load1加载玩数据之后再执行load2以及后续的加载数据
StoreStore
双写屏障
write1 //写操作
StoreStore //写屏障
write2 //写操作
确保 store1 在写入之前 不会执行 store2以及后续的写入
StoreLoad
写读屏障
write1 //写操作
StoreLoad //写读屏障
load1 //读操作
确保write1在写完数据后 load1才能加载数据
LoadStore
读写屏障
load1 //读操作
LoadStore //读写屏障
write1 //写操作
确保 load1在加载完数据之后 write1才能写数据
volatile
字节码编译 就是访问标记 也就是 0x0040 [volatile]
也就volatile 是JVM实现的
volatile 保证了读写的顺序性 和 可见性 但是不保证原子性
保证读写的顺序 采用的是内存屏障的方式 volatile 读操作
.... //之前的操作
LoadLoad 在之前的读完之后自己才能读取 在读操作之前让之前的读操作读完了之后自己再去读
volatile 读操作
StoreLoad 禁止下面的写操作和当前的读操作乱序,就是确保当前的读操作读完之后 后面的写操作才能执行
......//之后的操作
volatile 写操作
.... //之前的操作
StoreStore 在之前的写操作完成之后自己才能去写
volatile 写操作
LoadStore 在自己的写完成之后 其他的读操作才能去读
......//之后的操作
保证可见性
MESI协议是可以实现保证可见性
Lock指令 也就是在写操作前面添加Lock指令保证可见性 写完之后会将数据立刻刷写到主存储区中 Lock指令是CPU的指令
happens-before
happens-before表示的是前一个操作的结果对于后续操作是可见的,它是一种表达多个线程之间对于内存的可见性。所以我们可以认为在 JMM 中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作必须要存在happens-before关系。这两个操作可以是同一个线程,也可以是不同的线程