美文网首页
双重检验单例为什么要用volatile?

双重检验单例为什么要用volatile?

作者: 技术灭霸 | 来源:发表于2020-05-15 23:38 被阅读0次

不加volatile单例代码:

public class Singleton {
    private static Singleton s;
    private Singleton(){};
    public static Singleton getInstance() {  //1
        if(s == null) { //2
            synchronized (Singleton.class) { //3
                if(s == null) { //4
                    s = new Singleton(); //5
                }
            }
        }
        return s; //6
    }
}

在并发情况下,如果没有volatile关键字,在第5行会出现问题
对于第5行 s = new Singleton(); //5

可以分解为3个步骤:

  1. 分配内存 相当于c的malloc
  2. 初始化对象
  3. 设置s指向刚分配的地址

上面的代码在编译器运行时,可能会出现重排序 从1-2-3 排序为1-3-2

例如现在有2个线程A,B
线程A在执行第5行代码时,B线程进来,而此时A执行了 1和3,没有执行2,此时B线程判断s不为null(外层的判空) 直接返回一个未初始化的对象,就会出现问题

正确双重检验单例模式写法:

public class Singleton {
    private static volatile Singleton s;
    private Singleton(){};
    public static Singleton getInstance() {
        if(s == null) {
            synchronized (Singleton.class) {
                if(s == null) {
                    s = new Singleton();
                }
            }
        }
        return s;
    }
}

总结:加上volatile就是为了防止产生指令的重排序问题

为什么要两次判空?

1、第一个 if(instance==null),只有instance为null的时候,才进入synchronized的代码段大大减少了加锁的几率。
2、第二个if(instance==null),是为了防止可能出现多个实例的情况。

解释:当A与B同时调用getSingleton时,判断第一个if都为空,这时A拿到锁,进行第二层if判断,条件成立new了一个对象;

B在外层等待,A创建完成,释放锁,B拿到锁,进行第二层if判断,条件不成立,结束释放锁。C调用getSingleton时第一层判断不成立,直接拿到singleton对象返回,避免进入锁,减少性能开销。

相关文章

网友评论

      本文标题:双重检验单例为什么要用volatile?

      本文链接:https://www.haomeiwen.com/subject/tpekohtx.html