✅Java程序执行的过程
Java代码在编译后成为java字节码,字节码被类加载器加载到jvm中,jvm执行字节码,最终需要转化为汇编指令在cpu上执行。Java中所使用的并发机制依赖于jvm的实现和cpu的指令。
⭐️Volatile在多线程并发编程中保证了共享变量的“可见性”
⭐️标准定义:java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。
❓如何保证可见性的呢?
当一个变量被修饰为volatile时,在该变量被转变成汇编代码的时候,会比普通变量在最后多加一个操作指令:lock
内存读写模型(自己瞎理解版)
Lock指令会在多核处理器下引发两件事情:
1)将当前处理器缓存行的数据写回到系统内存。
2)这个写回内存的操作会使其他cpu里缓存了该内存地址的数据无效。
❓其他cpu如何知道自己存储的是无效的呢?
在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议:每个处理器通过嗅探在总线上(Q:什么总线,数据总线?地址总线?)传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置为无效状态,当处理器对这个数据进行修改操作时,会重新从系统内把数据读到处理器缓存里。
?volatile的两条实现原则的详细讲解
1)将当前处理器缓存行的数据写回到系统内存。
- Lock信号确保在声言该信号期间,处理器可以独占任何共享内存。【因为它会锁住总线,导致其他cpu不能访问总线,不能访问总线就意味着不能访问系统内存】
- 但是在最近的处理器中,不锁总线【花销大】,锁缓存。
- 在目前的处理器中,如果访问的内存区域已经在缓存在处理器内部中,则不会声言lock信号。
- 如果在缓存里,则会锁定此缓存并写回到内存,并使用缓存一致性机制来保证修改的原子性,此操作被称为“缓存锁定”。缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据。
2)这个写回内存的操作会使其他cpu里缓存了该内存地址的数据无效。
在多处理器情况下,处理器使用嗅探技术保证它的内部缓存、系统内存和其他处理器的缓存的数据在总线上保持一致。
例如:
如果通过嗅探一个处理器来检测其他处理器打算写内存地址,而这个地址当前处于共享状态,那么正在嗅探的处理器将会使它的缓存行无效,在下次访问相同内存地址时,强制执行缓存行填充。
?Volatile的优化——追加字节优化性能
在因特尔酷睿i7等等处理器中,缓存行的大小时64个字节宽。在其的队列集合类中,就是用追加字节的方式进行内存优化。
众所周知吗,队列集合类包含了头节点和尾节点。头节点和尾节点存放的是对象的引用【4个字节】。一个缓存行是64个字节,【处理器不支持部分填充,即必须要填充满一行才能填充下一行】,则代表头节点和尾节点将会在同一个缓存行里,在缓存一致性机制下,所有处理器的缓存都是这样。我们知道,当处理器要修改头节点时,需要将整个缓存行锁定,那么尾节点就被锁定了,在缓存一致性机制下,其他处理器就不能访问自己的尾节点了。而队列的入队出对需要频繁访问头尾节点。就影响了效率。
⭐️所以通过对头尾节点追加60个字节来保证填满缓存行,不会互相锁定。
这只是一种优化方式,而且并不适用于1)缓存行非64字节的处理器。 2)不会频繁读写