volatile与内存重排

前言

在学单例模式的时候,有一种懒汉式的方法可以保证线程同步问题,就是Double Check双重检查锁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton {
private volatile static Singleton singleton;

private Singleton() {}

public Singleton getInstance() {
if (null == singleton) {
synchronized (Singleton.class) {
if (null == singleton)
singleton = new Singleton();
}
}
return singleton;
}
}

这种机制保证了懒汉式创建单例的时候可以线程同步,但是看到代码的时候不能理解为什么需要加上volatile,查阅了一波资料,于是有了这篇笔记(。

什么是指令重排

在jvm中,有一种提升性能的方法叫做指令重排。比如在如下代码中

1
2
a = b + c;
z = x + y;

这两段代码如果按照顺序执行,那么需要如下步骤:(自行补操作系统知识)

  1. IF 取指令
  2. ID 指令译码
  3. EX 执行指令
  4. MEM 访存取数
  5. WB 结果写回

在现在的机器中,CPU指令的执行都是按照流水线方式执行的,大致流程如下图:

p1

那么如果不进行指令重排,我们执行指令的时候,顺序如下

b加载到寄存器–>c加载到寄存器–>a将b和c相加放到寄存器–>将寄存器的值保存到a–>x加载到寄存器–>y加载到寄存器–>z将x和y相加放到寄存器–>将寄存器的值保存到z

在执行bc相加的时候,会需要等待b和c加载到寄存器中才能执行相加指令,那么就需要等待,这样这段空闲等待时间就没有办法利用起来,于是我们调整顺序:

b加载到寄存器–>c加载到寄存器–>x加载到寄存器–>a将b和c相加放到寄存器–>y加载到寄存器–>将寄存器的值保存到a–>z将x和y相加放到寄存器–>将寄存器的值保存到z

在bc相加的空闲时间,我们将x加载到寄存器的指令提前,在停顿加载的时候去处理别的数据,有效的利用资源,这就是指令重排。

指令重排只发生在两个不相关的语句之间,在单线程环境下,指令重排并不会影响代码最终的执行结果。但是在多线程中,这种机制就会影响到最终的执行结果了。

as-if-serial语义:不管怎么重排序,单线程下程序的执行结果不能被改变。

new一个对象的过程

在执行singleton = new Singleton();这行代码的时候,需要执行三个步骤:

1、在堆区分配对象需要的内存

  分配的内存包括本类和父类的所有实例变量,但不包括任何静态变量

2、执行实例初始化代码

  初始化顺序是先初始化父类再初始化子类(双亲委派机制),初始化时先执行实例代码块然后是构造方法

3、将对象指向分配好的内存空间

volatile是什么

volatile是一个轻量级的线程同步机制。它的特性之一,是保证了变量在线程之间的可见性

可见性是指,当一个线程修改了某个变量的值,其他变量总能知道这个变量已经变化了。由于JMM的共享内存机制(每个线程维护自己的本地内存,里面存放着主内存的副本),如果一个线程修改了变量,其他线程的本地内存的副本不一定会进行及时更新,因此就造成了数据脏读。

对于这个问题,volatile保证了内存的可见性。当一个线程进入了volatile代码块后,线程会获取锁,然后清空所有本地缓存,然后从主内存中拷贝最新的副本到本地内存,执行代码后将副本覆盖到主内存中,然后释放锁,同时通知其他线程原有的副本变量已经改变,需要重新从主内存中拷贝。

同时,volatile禁止了指令重排序,使用了内存屏蔽指令。在每个 volatile 读操作的后面插入 LoadLoad 屏障和 LoadStore 屏障。在每个 volatile 写操作的前后分别插入一个 StoreStore 屏障和一个 StoreLoad 屏障。p2

双重检测

返回之前的问题,在单例模式中,new一个instance的操作是非原子性的,即我们如果不在代码块中加上volatile,那么new一个对象的操作2和操作3会因为指令重排导致执行顺序发生改变。那么就会造成instance对象还没有初始实例化,就已经指向了内存空间,获取整个对象的时候返回的就是一段空的内存,会造成程序错误。因此在多线程操作中,我们必须加上volatile保证这个对象已经初始化,之后再将对象指向分配好的内存空间,保证返回的单例是已经被创建好的。

文章目录
  1. 1. 前言
  2. 2. 什么是指令重排
  3. 3. new一个对象的过程
  4. 4. volatile是什么
  5. 5. 双重检测
|