JMM中的可见性问题
在多线程环境中,共享变量A(存储在主存)在每个线程的工作内存中有一份拷贝Bi,线程直接读写工作内存中的数据,不同的线程的工作内存中变量之间不可见,这样就可能存在一个问题:工作内存Bi与主内存A同步延迟现象带来的可见性问题
volatile修饰的变量特点
1. 多线程之间缓存可见
2. 禁止指令重排,保证数据的有序性/一致性
volatile 缓存可见性(使用lock汇编指令)
线程对内存的读写操作,先和自己的工作内存进行交互。线程的工作内存是私有的其他线程不可见,所以线程必须通过缓存一致性协议来读取的其他线程修改过的数据,同步到自己的工作内存中。volatile保证:
1. 将当前线程修改的数据立刻写回主内存中,使一个线程对变量的修改对其他线程立即可见
2. 开启MESI缓存一致性协议,写回内存时触发总线嗅探机制,使其他线程工作内存中的变量副本失效,重新read/load主存中的变量
volatile数据有序性/一致性
单线程环境下,代码按顺序执行,多线程环境下,计算机为了提高执行效率,编译器和处理器往往会进行指令重排(保证数据依赖的情况下),导致程序乱序执行,结果可能存在不一致的问题。volatile采用内存屏障机制禁止指令重排序优化,从而避免了多线程环境下程序出现乱序执行的现象(多线程环境中,单例模式必须借助volatile禁止指令重排序,避免得到未初始化的实例:
Instance instance = new Instance(); ->1.在堆内存上分配对象的内存空间;2.在堆内存上初始化对象3. instance指向刚分配的内存地址。2和3可能会发生重排序,导致instance指向了一个不为null但未初始化的实例对象)
volatile不能保证原子性
Volatile单操作保证变量原子性,但不能保证读写操作原子性(可能再写入之前有多个线程同时读)。volatile+CAS保证原子性,例如java并发包下的Atomic等类 【Java 基础 12】Atomic
volatile性能
volatile变量读操作性能消耗和普通变量几乎没有差别,但是写操作会慢一些,因为需要在本地代码中插入许多内存屏障指令来保证处理器不出现乱序执行。不过即便如此,大多数场景下,volatile的总开销要比锁来的低(volatile可以说时Java虚拟机提供的最轻量的同步机制)。