1.volatile的有序性-单例模式双重检测锁为什么要用volatile?

本篇文章想讲述的问题:

  1. 单例模式-双重检测锁为什么要用volatile关键字
  2. 上下代码有依赖关系时指令重排后的执行顺序

单例模式-双重检测锁为什么要用volatile关键字

先看代码:

public class Singleton {
    static Singleton instance;

    public Object o;

    public Singleton() {
        this.o = new Object();
    }

    static Singleton getInstance(){
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

假设现在有两个线程A和B,A现在先进入了第2个if判断并执行第14行代码对对象进行初始化。A线程进入到构造方法对对象进行初始化时,由于JVM会对指令进行重排,所以初始化的代码顺序可能是:

第一种顺序:

  1. 分配一块内存 M;
  2. 在内存 M 上初始化 Singleton 对象(对属性o进行初始化和赋值);
  3. 然后 M 的地址赋值给 instance 变量。

第二中顺序(实际上优化后的执行路径可能是这样的):

  1. 分配一块内存 M;
  2. 将 M 的地址赋值给 instance 变量;
  3. 最后在内存 M 上初始化 Singleton 对象(对属性o进行初始化和赋值)。

 假设虚拟机对指令重排优化后,按第二种顺序执行。A线程现在执行到第二步将 M 的地址赋值给 instance 变量;现在cpu对线程切换并切换到B线程上,B此时若走到第一个if判断,此时instance已不为空,所以直接返回instance(此时A线程还没有完成对instance变量引用的对象完成初始化也没有执行到第16行对对象锁进行释放)。因为此时B返回的instance的属性o没有被初化或赋值,所以B线程返回的instance对象o属性为空,如果B线程对返回的instance对象的o属性进行方法调用可能出现空指针异常。

因此解决办法:对instance加上volatile关键字。

原因:volatile可以禁止JVM和处理器对指令进行重排。

volatile并不是可以禁止所有的重排,用以下代码来解释:

    volatile int m;
    int a, b;
    long c, d;
    public void testVolatile() {
        a = 10;
        b = 10;
        m = 20;
        c = 30;
        d = 40;
    }

 volatile能保证的顺序是:对volatile修饰的变量进行操作的语句的前面的语句,一定比对volatile修饰的变量进行操作的语句先执行。对volatile修饰的变量进行操作的语句的后面的语句,一定比对volatile修饰的变量进行操作的语句后执行。有点绕,简单说也就是:testVolatile方法被执行时,m = 20前面两行代码一定比m = 20要先执行(不能保证a = 10; b = 10;这两句的执行先后顺序);m = 20后面两行代码一定比m = 20要后执行(不能保证c = 30; d = 40;这两句的执行先后顺序)。

这就解释了为什么单例模式的双重检测锁为什么要用volatile关键字,以及volatile关键字是如何禁止指令重排的。

上下代码有依赖关系时指令重排后的执行顺序

代码示例:

public void method() {
     int a = 0;
     a = 10;
     int b = 0;
     b = 20;
 }

这个method方法,看上去可能是按写的代码的顺序执行的,执行顺序是:先执行第2行,再执行第3、4、5行。但是代码执行顺序,往往会被这个编译器优化,进行指令重排序。优化后的执行顺序可能是:先执行第4、5行,再执行第2、3行。也可能是:先执行第4、2行,再执行第3、5行。还有其它执行顺序的可能。但是有一点是确定的:因为第3行依赖第2行,所以第3行一定会在第2行后面执行,也就是第2行一定比第3行先执行;同理第4行也一定比第5行先执行;不然执行就会报错我有问题。

以上是个人观点,若又不正确的地方欢迎指正。

你可能感兴趣的:(08_多线程和并发)