单例模式的双重检查
双重检查
public class Singletone{
private static Instance instance;
public Instance getInstance(){
if(instance == null){
synchronized(Singletone.class){
if(instance == null){
instance = new Instance();
}
}
}
return instance;
}
}
问题:
instance = new Instance();
是由三个步骤组成的:
- 为对象分配内存
- 实例化对象
- 将引用指向对应的内存地址
但是第2,3步可能发生指令重排列,导致先将引用指向一个未实例化对象的内存地址,然后再进行实例化对象。
若此时第二个线程进行第一个非空判断时,则为false,会直接返回还没有实例化对象的内存地址,从而可能产生异常。
解决:
- 禁止指令重排列
- 允许指令重排列,但是其他线程“不可见”
方案一:基于volatile
禁止指令重排列
public class Singletone{
private volatile static Instance instance;
public Instance getInstance(){
if(instance == null){
synchronized(Singletone.class){
if(instance == null){
instance = new Instance();
}
}
}
return instance;
}
}
在成员变量中加入volatile
变量,禁止使用new创建对象时的指令重排列。
方案二:基于类初始化的解决方案
public class Singletone{
private static class InstanceHolder{
public static Instance instance = new Instance();
}
public static Instance getInstance(){
return InstanceHolder.instance;
}
}
JVM在类初始化阶段进行类的初始化。在初始化期间,JVM会获取一个锁,从而同步多个线程对同一个类的初始化。
第一个获得锁的线程会完成实例对象的创建,其他线程会直接获取创建的实例对象。
参考: