Java内存模型
想要理解 volatile 为什么能确保可见性,就要先理解Java中的内存模型是什么 样的。 Java内存模型规定了所有的变量都存储在主内存中。每条线程中还有自己的工作内 存,线程的工作内存中保存了被该线程所使用到的变量(这些变量是从主内存中拷 贝而来)。线程对变量的所有操作(读取,赋值)都必须在工作内存中进行。不同 线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过 主内存来完成。

volatile的实现原理
- 可见性
处理器为了提高处理速度,不直接和内存进行通讯,而是将系统内存的数据独到内 部缓存后再进行操作,但操作完后不知什么时候会写到内存。 如果对声明了volatile变量进行写操作时,JVM会向处理器发送一条Lock前缀的指 令,将这个变量所在缓存行的数据写会到系统内存。 这一步确保了如果有其他线程 对声明了volatile变量进行修改,则立即更新主内存中数据。但这时候其他处理器的缓存还是旧的,所以在多处理器环境下,为了保证各个处理 器缓存一致,每个处理会通过嗅探在总线上传播的数据来检查 自己的缓存是否过 期, 当处理器发现自己缓存行对应的内存地址被修改了,就会将当前处理器的缓存 行设置成无效状态,当处理器要对这个数据进行修改操作时,会强制重新从系统内 存把数据读到处理器缓存里。 这一步确保了其他线程获得的声明了volatile变量都是 从主内存中获取最新的。
2.有序性
Lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),它确保指令重排序时 不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障 的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成。
volatile应用场景
- volatile 修饰单例模式里面的instance
class Singleton {
private volatile static Singleton instance = null;
private Singleton() { }
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}
- 为什么要使用volatile 修饰instance?
主要在于instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情:
- 给 instance 分配内存
- 调用 Singleton 的构造函数来初始化成员变量
- 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)。
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第 三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是 后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺 理成章地报错。
网友评论