- 共享资源是否有多个线程同时访问
- 希望结果跟预期的一致
Volatile
作用:保证共享资源的可见性
如何保证可见性(hsdis工具)?
通过反编译可以看到多了一个汇编Lock指令,相当于下面说的内存屏障的功能
什么是可见性?
硬件层面

cpu的高速缓存:分为L1(指令缓存)、L2(数据缓存)、L3 -->性能逐步下降
为了最大化利用cpu资源:优化了如下模块
- cpu增加高速缓存
- 引入线程、进程
- 指令优化:重排序
硬件层面解决可见性
:
- 总线锁:处理器之间的操作互斥->性能差
cpu获取总线锁,总线发出lock信号,其他处理器不能操作主内存
- 缓存锁:降低锁力度
如何实现缓存锁?通过MESI协议


cpu缓存一致性协议(MESI):缓存的4种状态
- M (Modify) 共享数据缓存在当前cpu中,并且是修改状态,即缓存数据和主内存不一致
- E (Exclusive) 数据缓存在当前cpu中,独占并且没有被修改
- S (Shared) 数据被多个cpu缓存,并且与主存一致
- I (Invalid) 表示缓存已经失效
读请求:M、E、S状态的缓存可以被读取,I状态只能从主存
中读取
写请求:M、E状态可以写,S状态需要使其他CPU缓存失效
**引入缓存锁后仍存在的问题:**处于S状态的cpu进行写请求的时候需要跟其他CPU通信
,这就会存在阻塞问题
因此引入storebuffer缓冲区(相当于异步处理),在cpu当中,主内存读取先读buffer,但是仍有重排序等问题

在没有屏障指令的情况下,两个cpu同时执行,cpu0对于value=10先写入storebuffer在通知cpu1,继续执行isFinish,因为e状态可以直接修改修改。cpu1读取到isFinish为true,但此时的value还未修改为10,因此assert判断为false
CPU层面提供了指令->内存屏障
内存屏障:用来解决可见性问题
CPU层面提供了3种屏障
- 写屏障:写之前的缓存同步到主存
- 读屏障:基于失效队列
- 全屏障
JMM的内存模型层面
导致可见性
的根本原因:高速缓存和重排序
JMM的核心
是解决了有序性和可见性
可见性和有序性解决方式:通过volatile、synchronized、final、happens-before
重排序的类型:
- 编译重排序
- CPU层面的重排序:指令重排序、内存重排序
不会进行重排序的场景:
- 数据依赖规则
as-if-serial原则:不管如何重排序,结果不能变
JVM语言级别的内存屏障:
loadload、storestore、loadstore、storeload、
Happens-Before规则
- 程序顺序规则:单线程对于结果来说是不变的
- volatile变量规则:volatile修饰的变量,写操作对于后续读操作可见
- 传递性规则:1 h-b 2,2 h-b 3,则1 h-b 3
- start规则:线程a中启动b线程,则启动前的数据对于线程b可见
- join规则:线程b join 线程a,则线程a的共享变量操作对于b可见
- 监视器锁规则:对于锁(synchronized)的解锁对于这个锁的加锁可见,即多线程之间synchronized代码块之间是顺序可见
- 线程中断规则:对线程interrupt方法的调用happend-before线程中断检测
- 对象创建规则:对象初始化完成happend-before它的finalize()方法
常见问题:
MESI协议实现可见性和有序性:
无法完全实现。由于MESI store buffer当中是异步的,可能会造成有序性问题,因此通过内存屏障
来解决