Volitile的主要作用就是保持内存可见性和防止指令重排序。我分别说一下这两个作用的实现原理
1.保持内存可见性的实现原理
volatile内存可见性主要通过lock前缀指令实现的,它会锁定当前内存区域的缓存,并且立即将当前缓存的数据写入到主内存(耗时非常短),回写主内存的时候通过MESI协议,使其他线程缓存了该变量的地址失效,从而导致其他线程需要重新去主内存中重新读取数据到其工作线程中。
那么什么是MESI协议呢?
全称Modified,Exclusive,Shared,Invalid,是一种高速缓存一致性协议,它是为了解决多处理器(CPU)在并发环境下,多个缓存不一致的问题而提出的。
Modified(M–修改):表示缓存已经被修改,但是还没被写回主内存,这种状态下,只有一个CPU能独占这个修改状态。
Exclusive(E–独占):表示缓存与主存储器的数据相同,并且缓存的数据是主存储器的唯一拷贝,在这种状态下,只有一个cpu能独占这个状态
Shared(S–共享):表示此高速缓存可能存储在计算机的其他高速缓存中,并且与主存储器匹配,在这种状态下,各个CPU可以并发的对这个数据进行读取,但是都不能进行写操作。
Invalid(I–失效):表示此缓存已经失效或已经过期,不能使用。
MESI 协议的主要用途是确保在多个 CPU 共享内存时,各个 CPU 的缓存数据能够保持一致性。当某个 CPU 对共享数据进行修改时,它会将这个数据的状态从S(共享)或 E(独占)状态转变为 M(修改)状态,并等待适当的时机将这个修改写回主存储器。同时,它会向其他 CPU 广播一个“无效消息”,使得其他 CPU 将自己缓存中对应的数据状态转变为I(无效)状态,从而在下次访问这个数据时能够从主存储器或其他 CPU 的缓存中重新获取正确的数据。
这种协议可以确保在多处理器环境中,各个 CPU 的缓存数据能够正确、一致地反映主存储器中的数据状态,从而避免由于缓存不一致导致的数据错误或程序异常。
2.有序性实现原理
volatile 的有序性是通过插入内存屏障 (Memory Barrier),在内存屏障前后禁止重排序优化,以此实现有序性的。在 Java 内存模型(JMM)中,volatile 关键字用于修饰变量时,能够保证该变量的可见性和有序性。关于有序性volatile 通过内存屏障的插入来实现:
写内存屏障(Store Barrier/Write Barrier): 当线程写入 volatile 变量时,JMM 会在写操作前插入StoreStore 屏障,确保在这次写操作之前的所有普通写操作都已完成。接着在写操作后插入 StoreLoad 屏障强制所有后来的读写操作都在此次写操作完成之后执行,这就确保了其他线程能立即看到 volatile 变量的最新值。
读内存屏障(Load Barrier/Read Barrier): 当线程读取 volatile 变量时,JMM 会在读操作前插入LoadLoad 屏障,确保在此次读操作之前的所有读操作都已完成。而在读操作后插入 LoadStore 屏障,防止在此次读操作之后的写操作被重排序到读操作之前,这样就确保了对 volatile 变量的读取总是能看到之前对同一变量或其他相关变量的写入结果。
那么什么是内存屏障呢?
内存屏障(Memory Barrier 或 Memory Fence)是一种硬件级别的同步操作,它强制处理器按照特定顺序执行内存访问操作,确保内存操作的顺序性,阻止编译器和 CPU 对内存操作进行不必要的重排序。内存屏障可以确保跨越屏障的读写操作不会交叉进行,以此维持程序的内存一致性模型。