本文主要是介绍单例模式双重检验锁的volatile和两次判空,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
首先是代码,经典的双重锁写法
public class Singleton {
private volatile static Singleton Instance;
private Singleton(){}
public static Singleton getInstance(){
if(Instance == null){
synchronized(Singleton.class){
if(Instance == null){
Instance = new Singleton();
}
}
}
return Instance;
}
}
先说为什么需要两次判空的原因?
第一次判断是为了验证是否创建对象,避免多线程访问时每个线程都加锁,提升效率第二次判断是为了避免重复创建单例,因为可能会存在多个线程通过了第一次判断在等待锁,来创建新的实例对象。
例如:有三个线程,A与B同时调用getSingleton时,判断第一个if都为空,这时A拿到锁,进行第二层if判断,条件成立new了一个对象;由于Synchronized原因,B在外层等待,A创建完成,释放锁,B拿到锁,进行第二层if判断,条件不成立,结束释放锁。C调用getSingleton时第一层判断不成立,直接拿到singleton对象返回,避免进入锁,减少性能开销。
在说说为什么有了Synchronized却还需要volatile去修饰Instance。
volatile修饰变量只是为了禁止指令重排序,因为在 Instance = new Singleton(); 创建对象时,底层会分为四个指令执行:(下面是正确的指令执行顺序)
①、如果类没有被加载过,则进行类的加载
②、在堆中开辟内存空间 adr,用于存放创建的对象
③、执行构造方法实例化对象
④、将堆中开辟的内存地址 adr 赋值给被volatile修饰的Instance引用变量
如果Instance引用变量不使用volatile修饰的话,则可能由于编译器和处理器对指令进行了重排序,导致第④步在第③步之前执行,此时Instance引用变量不为null了,但是Instance这个引用变量所指向的堆中内存地址中的对象是还没被实例化的,实例对象还是null的;那么在第一次判空时就不为null了,然后去使用时就会报NPE空指针异常了。
原文链接:https://blog.csdn.net/weixin_45398467/article/details/108893962
这篇关于单例模式双重检验锁的volatile和两次判空的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!