Java 并发机制的底层实现
volatile
文章目录
volatile的定义和实现原理
java语言规范中对其的定义:
Java编程语言允许线程访问共享变量,为了确保共享变量被准确和唯一的更新,线程应该确保通过排他锁去单独获得这个变量;
一、使用volatile时处理器中做的事
- 将当前处理器缓存行的数据写回到系统内存
- 这个写回内存的操作会使得其他cpu里缓存了该内存地址的数据无效;
(1)Lock前缀指令会引起处理器缓存回写到内存
他会锁定这块内存区域对应的缓存并回写到内存,并用缓存一致性机制来确保修改的原子性,此操作被称为缓存锁定;缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域储存;
(2)一个处理器的缓存回写到内存会导致其他处理器的缓存无效;
MESI(修改,独占,共享,无效)控制协议去维护内部缓存和其他处理器缓存的一致性。(只锁cache
line)
如果通过嗅探一个处理器来检测其他处理器打算写内存地址,而这个地址当前处于共享状态,那么正在嗅探的处理器将使它的缓存行无效,下次访问相同内存地址的时候将强制执行行填充;
如果通过嗅探一个处理器来检测其他处理器打算写内存地址,而这个地址当前处于共享状态,那么正在嗅探的处理器将会使得它的缓存行无效,下次访问相同内存地址的时候,强制执行缓存填充;
这样做就没有用到总线锁,总线锁锁住了整个内存,CPU性能下降;
二、volatile的使用优化
LinkedTransferQueue
LinkedTransferQueue,使用一种追加变量的方法,使得一个节点占满一个cache line,这样使得这个队列的头结点和尾结点不可能在一个cache line之中;由于volatile执行的MESI协议,锁定的是cache line,所以访问头结点的时候尾结点所在的cache line不会被锁定;
什么时候不该追加到64字节
- 缓存行并非是64字节
- 添加更多的字节本身也是一种消耗,如果共享变量并没有频繁读写的话,锁没有被使用,也是一种浪费资源;
ps:这种追加字节的方式在Java7下不能生效,因为Java7变得指挥,会淘汰或者重新编排没有用的字段,就要用其他方式追加字节;
总结
volatile通过MSEI协议去锁cache line 而非锁总线来提高CPU的效率,实现了”可见性“;并且Doug Lea通过TransferQueue用一种填充字节的方法进行了优化。
后期对volatile没有原子性的个人理解:
- volatile使用的MESI这个协议配合ringbus实现的一个缓存锁,没有使用总线锁,当然缓存锁也是一种解决原子性的办法;
- volatile的锁是由LOCK指令带来的,这个LOCK指令可以理解为内存屏障,使得代码按顺序执行,也可以说它是上了一个缓存锁
- 疑问:既然有缓存锁为什么还没有原子性呢?
- 其实可以理解为volatile的指令问题,LOCK这条指令是在写命令之后才执行,意味着当一个线程A去修改对象的值的时候,如果有线程B已经获取了这个对象的值但没有进行修改,线程B其实并没有对其进行上锁,而是把这个对象的值已经读到寄存器中去了,之后用的值是未更新的值;