public class SingletonClass{
private static SingletonClass instance=null;
public static SingletonClass getInstance(){
if(instance==null){
synchronized(SingletonClass.class){
if(instance==null){
instance=new SingletonClass();
}
}
}
return instance;
}
private SingletonClass(){}
}
原理:
当两个线程同时调用这个方法的时候,如果一个判空可能会创建两个对象,比如当A线程还没创建完对象的时候,B线程也能进入if分支里面,所以使用synchronized加锁排队;如果每次都排队的话,效率就低下了,所以需要外层的判空。
总结:synchronized保证线程安全,外层判空保证效率。
升级版:
单例instance使用volatile修饰,为了禁止指令重排
public class SingletonClass{
private volatile static SingletonClass instance=null;
public static SingletonClass getInstance(){
if(instance==null){
synchronized(SingletonClass.class){
if(instance==null){
instance=new SingletonClass();
}
}
}
return instance;
}
private SingletonClass(){}
}
在说说为什么有了Synchronized却还需要volatile去修饰Instance。
volatile修饰变量只是为了禁止指令重排序,因为在 Instance = new SingletonClass(); 创建对象时,底层会分为四个指令执行:(下面是正确的指令执行顺序)
①、如果类没有被加载过,则进行类的加载
②、在堆中开辟内存空间 adr,用于存放创建的对象
③、执行构造方法实例化对象
④、将堆中开辟的内存地址 adr 赋值给被volatile修饰的Instance引用变量
如果Instance引用变量不使用volatile修饰的话,则可能由于编译器和处理器对指令进行了重排序,导致第④步在第③步之前执行,此时Instance引用变量不为null了,但是Instance这个引用变量所指向的堆中内存地址中的对象是还没被实例化的,实例对象还是null的;那么在第一次判空时就不为null了,然后去使用时就会报NPE空指针异常了。