文章目录
Volatile原理
1.Volatile语义中的内存屏障
在Java代码中,volatile关键字主要又两层语义
- 不同线程对volatile变量的值具有内存可见性,就是一个线程修改了某个volatile变量的值,该值对其他线程立即可见。
- 禁止指令重排序
同时volatile关键字不仅能保证可见性,还能保证有序性,保证有序性是通过内存屏障指令来确保的。
JVM编译器会在生成字节码文件时,会在指令序列中插入内存屏障来禁止特定类型的CPU重排序。
JVM在处理volatile
关键字修饰的变量时,会采取保守策略来确保内存可见性和有序性,这涉及到内存屏障(Memory Barrier)的使用。内存屏障是一种硬件层面的指令,用于确保某些内存访问操作的执行顺序,防止CPU的乱序执行对并发程序的正确性产生影响。对于volatile
变量的读写,JVM会分别在读和写操作前后插入适当类型的内存屏障,确保以下几点:
- 全局可见性: 保证对
volatile
变量的写操作能立即对其他线程可见,即使在不同的CPU缓存中也是如此。这意味着写操作之后,修改的值会立刻刷出到主内存中。 - 禁止重排序: 防止编译器和CPU对涉及
volatile
变量的代码进行不必要的重排序,确保它们按照程序员指定的顺序执行。这对于依赖于特定顺序的并发控制逻辑至关重要。
基于保守策略的volatile
操作内存屏障插入策略主要包括以下方面:
- 写屏障(Store Memory Barrier): 在写入
volatile
变量之后插入。它的作用是确保在该屏障之前的所有普通写操作(非volatile
)都已完成,并且将当前线程的工作内存中的volatile
变量值刷新到主内存中。这样,任何读取该volatile
变量的线程都能看到最新值。- 在每个volatile
写
操作前
面插入一个StoreStore
屏障 - 在每个volatile
写
操作后
面插入一个StoreLoad
屏障
- 在每个volatile
- 读屏障(Load Memory Barrier): 在读取
volatile
变量之前插入。它的作用是确保读取操作之后的加载不会被重排序到该屏障之前,并且使CPU读取主内存中的最新值,而不是使用缓存中的旧值,从而确保读取到的是最近一次写入的值,无论这个写入操作发生在哪个线程中。- 在每个volatile
读
操作前
面插入一个LoadLoad
屏障 - 在每个volatile
读
操作后
面插入一个LoadStore
屏障
- 在每个volatile
这些屏障的联合使用确保了对volatile
变量的读写操作具有原子性和全局有序性,尽管它们不保证复合操作(如count++
)的原子性。这就是为什么即使在没有锁的情况下,volatile
也能作为轻量级的同步机制,用于状态标记、双重检查锁定模式等场景。
1.1.volatile写操作的内存屏障
volatile写操作的内存屏障插入策略为:在每个
volatile
写操作前插入SotreStore(SS)
屏障,在写操作之后加上StoreLoad
屏障
1.1.1.StoreStore 屏障
定义与作用: StoreStore
屏障主要用于确保一个存储(写)操作在另一个存储操作之前完成。换句话说,它强制所有在该屏障之前的存储操作在该屏障之后的存储操作之前完成。这种屏障通常用于避免写-写冲突导致的数据不一致问题,尤其是在处理器有乱序执行能力的体系结构中。
- 前面的写入不会重排序到后面
- 前面的写指令完成后,高速缓存数据刷入主存
- 后面的写操作不会排序到前面
应用场景: 例如,在实现某些类型的锁释放操作时,可能需要确保解锁操作前的所有写操作已经完成,以免新获得锁的线程看到不一致的状态。
1.1.2.StoreLoad 屏障
定义与作用: StoreLoad
屏障是Java内存模型中最强大的一种屏障,它确保在屏障之前的所有写操作(存储操作)在屏障之后的任何读操作(加载操作)之前完成。这意味着不仅要求写操作完成,而且要确保这些写操作对所有线程可见。因此,StoreLoad
屏障通常用于实现volatile