涉及知识点
硬件级别的可见性 硬件级别的原子性 硬件级别的有序性

和MESI协议对应,flush处理器缓存,refresh处理器缓存 flush: 将自己更新的值刷新到高速缓存或者主内存,并发送消息到总线bus,通知其他处理器
1.volatile
写操作:执行flush命令,刷新数据到高速缓存或主内存,发送消息到总线 读操作:会执行refresh操作,从其他处理器的高速缓存或者主内存读取到最新的值 原子性:32位jvm中long/double等64位的数据 可见性:读之前会加load屏障,写之后会家store屏障 有序性:volatile读写前后会加屏障,避免跟前后的读写操作发生指令重排
2.synchronized并发相关
原子性:ObjectMonitor来实现,通过CAS 可见性:通过Load和Store内存屏障来实现,monitorenter指令之后,强制执行load屏障,refresh最新数据到高速缓存,确保获取到最新数据。monotorexit之后,强制执行store屏障,将高速缓存的数据强制刷到高速缓存或者主内存 有序性:acquire屏障和release屏障,保障 synchronized
内部可以进行指令重排,外部不可用进行指令重排。对于Acquire来说,保证Acquire后的读写操作不会发生在Acquire动作之前 对于Release来说,保证Release前的读写操作不会发生在Release动作之后
3.可能指令重排序的几个地方

javac编译的时候可能会对代码编译进行重排序 JIT执行命令编译的时候可能会对指令进行重排序 处理器中也可能会对指令进行重排序 还有内存中也可能会重排序,主要原因是告诉缓存,无效队列以及写缓冲器之间可能会存在执行顺序不一致的情况
4.JIT可能存在指令重排的几个地方
以以下代码为例
public class Demo {
private Resource resource;
public Demo() {
this.resource = loadResource(); // 从配置文件里加载数据构造Resource对象
}
public void execute() {
this.resource.execute();
}
}
代码执行逻辑
- 1.Demo demo = new Demo()
- 2.demo.execute();
步骤1的执行在内jvm中其实是分为三步来执行的
- 1.给该对象分配一块内存
- 2.在该内存区域中执行构造方法,构造函数中存在比较耗时的操作
- 3.将demo指向该内存区域
当多线程执行代码逻辑时候,由于第2步比较耗时,所以指令重排序之后,可能存在先执行1,3,最后才执行2的可能。
4.硬件级别的缓存一致性协议与MESI协议

M:Modify状态:其他cpu无法读取到数据 E:Exclusive:独占的,其他数据无法获取这个副本。 I:Invalidate消息:无效消息 S: Share共享
4.1.对于共享变量的写入操作
cpu0:数据直接写入写缓冲器,同时发送 invalidate
消息,此后继续工作,非阻塞cpu1:其他处理器嗅探到 invalidate
消息之后,会直接写入无效队列,返回invalidate ack
的消息cpu0:cpu0嗅探到 invalidate ack
消息之后,将数据从写缓冲器重拿出来,并设置为E独占锁
,同时修改数据,改为M状态
cpu1: 读取该数据时发现状态是 I
,发送消息到总线去读取消息,cpu0同步数据,将cpu1和cpu0中的数据改为S共享。
4.2.读取高速缓存中不存在的数据
若cpu读取高速缓存中的数据,高速缓存中不存的话,则直接通过总线从主内存读取
5.可见性和有序性
可见性的问题,就是因为写缓冲器和无效队列导致。因为写数据不一定立即更新到自己的告诉缓存,读数据也不一定立即从高速缓存中获取,因为 invalidate
消息在无效队列中。