volatile的定义
如果一个共享变量被声明成volatile,Java线程内存模型确保所有线程看到的这个变量是一致的。
CPU术语定义
- 内存屏障:是一组处理器指令,用于实现对内存操作的顺序限制
- 缓冲行:缓存在L1级、L2级或L3级缓存中的最小单位
- 原子操作
- 缓存命中:CPU读取的数据,不需要走主内存,而是直接从缓存中获取的。则称缓存命中
- 写命中:修改的变量需要写回内存,如果此时这个变量存在与缓存中且未失效,就可以直接写回缓存
-
volatile的实现原理
lock指令的作用
将缓存行中的数据写回系统的主内存
- 将该变量在其他的缓存中的缓存置为无效
CPU厂商如何实现?:
- 缓存一致性协议
不加volatile时:
- 也就是说若没有volatile关键字,共享变量的写是不会及时的写回内存的
- 就算及时写回了内存,也不会将其他CPU中的缓存数据转变为失效状态
小结: 加了volatile后,生成lock指令,导致CPU会利用缓存一致性协议保证各个线程读取同一个共享变量的值一致 不加volatile后,不会生成lock指令。CPU就不会利用缓存一致性协议。
实现原则
缓存写回内存
- Lock#信号声言可以保证处理器独占任何共享内存。
- Lock#信号一般不锁总线而是锁缓存
- P6和最近处理器,如果内存区域已经缓存到本CPU的内部时,不会声言LOCK#
- 通过缓存一致性保证修改原子性和阻止两个以上处理器修改内存区域
结论:LOCK前缀(加了volatile)的共享变量CPU会利用缓存一致性规则,保证其修改的原子性和排他性
其他缓存失效
缓存一致性协议
MESI
奇怪的写法
在一些源码中可能会看到奇怪的变量声明。其目的是为了填充缓存行。比如我要修改a变量和b变量。我们的想法是修改A的互相等待和修改B的互相等待。而一个线程修改A,一个修改B的线程应该不用互等。但是由于缓存行会凑齐64字节,所以此时A和B会被发在同一个缓存行。这就导致修改A和修改B的线程互等。因此选择填充无用字段,让A、B分在不同的缓存行。
何时无需这样写:
- 缓存行本身64字节
- 共享变量不会频繁修改
另外:这种方式可能不生效,因为Java7会淘汰或重排列无用字段