内存模型底层八大原子操作
read(读取):从主内存读取数据
lead(载入):将主内存读取到的数据写入工作内存
use(使用):从工作内存读取数据来计算
assign(赋值):将计算好的值重新赋值到工作内存中
store(存储):将工作内存数据写入主内存
write(写入):将store过去的变量值赋值给主内存中的变量
lock(锁定):将主内存变量加锁,标识为线程独占状态
unlock(解锁):将主内存变量解锁,解锁后其他线程可以锁定该变量
缓存一致性协议(MESI)
多个cpu从主内存读取同一个数据到各自的高速缓存,当其中某个cpu修改了缓存里的数据,该数据会马上同步回主内存,其他cpu通过总线嗅探机制可以感知到数据的变化从而将自己缓存里的数据失效。
M:修改(Modified)
E:独享、互斥(Exclusive)
S:共享(Shared)
I:无效(Invalid)
volatile缓存可见性实现原理
并发编程的三大特性:
可见性,有序性,原子性
如何保证?
volatile保证可见性和有序性,但是不保证原子性,保证原子性需要借助synchronized这样的锁机制
volatile底层实现主要是通过汇编lock前缀指令,他会锁定这块内存区域的缓存(缓存行锁定)并写回到主内存
lock指令:
1.会将当前处理器缓存行的数据立即写回到系统内存
2.这个写回内存的操作会引起在其他cpu里缓存了该内存地址的数据无效(MESI协议)
3.提供内存屏障功能,使lock前后指令不能重排序
不同CPU硬件对于JVM的内存屏障规范实现指令不一样,而JVM底层简化了内存屏障硬件指令的实现:
lock前缀:lock指令不是一种内存屏障,但是他能完成类似内存屏障的功能
内存屏障类型:
LoadLoad:保证load1的读取操作在load2及后续读取操作之前执行
StoreStore:在store2及其后的写操作执行前,保证store1的写操作已刷新到主内存
LoadStore:在store2及其后的写操作执行前,保证load1的读操作已读取结束
StoreLoad:保证store1的写操作已刷新到主内存之后,load2及其后的读操作才能执行
指令重排序
在不影响单线程程序执行结果的前提下,计算机为了最大限度的发挥机器性能,会对机器指令重排序优化
源代码-->编译器优化重排序-->指令级并行重排序-->内存系统重排序-->最终执行的指令序列
重排序会遵循as-if-serial和happens-before原则
as-if-serial语义:不管怎么重排序,(单线程)程序的执行结果不能被改变。
所以编译器和处理器不会对存在数据依赖关系的操作做重排序,这种重排序会改变执行结果。
happens-before原则:
1.程序顺序原则:在一个线程内必须保证语义串行性(按照代码顺序执行)
2.锁规则:解锁(unlock)操作必然发生在后续的同一个锁的加锁(lock)之前
3.volatile规则:volatile变量的写,先发生于读,这保证了volatile的可见性(volatile变量在每次被线程访问时,都强迫从主内存中读该变量的值;而当该变量发生变化时,又会强迫将最新的值刷新到主内存,任何时刻,不同的线程总是能够看到该变量的最新值)
4.线程启动规则:线程的start()方法先于他得每一个动作(如果线程A在执行线程B的start方法之前修改了共享变量的值,那么当线程B执行start方法时,线程A对共享变量的修改对线程B可见)
5.传递性:A先于B,B先于C,那么A必然先于C
6.线程终止规则:线程的所有操作先于线程的终结。
7.线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测线程是否中断。
8.对象终结规则:对象的构造函数执行,结束先于finalize()方法