一、先看一个线程安全的DCL(double check lock)单例例子
public class SingletonUser {
//有可能拿到一个空的对象
private static SingletonUser instance = null;
private SingletonUser() {}
public SingletonUser getInstance(){
if(instance ==null){
synchronized (SingletonUser.class){
if(instance ==null){
instance = new SingletonUser();
}
}
}
return instance;
}
}
二、分析
这个看似安全的单例模式,在竞态条件下会出现一个半初始化对象的问题。那么我们要从创建单例对象这个语句说起了,这个语句实际在jvm是分成三步完成的,上伪代码,这一段代码在jvm进行优化的时候会乱序,有可能会变成1、3、2的执行顺序
instance = new SingletonUser ();
//这个new 并且赋值的语句在jvm中其实可以抽象成三条指令
memory = allocate(); //1:给对象开辟一块内存
initInstance(memory); //2:初始化对象
instance = memory; //3:instance指向分配好的内存
当线程A执行到对象引用执行分配好的内存时,这时对象还未初始化,线程B此时调用getInstance()方法,判断引用已经不为null,因此直接返回,此时对象是半初始化状态,使用会导致异常出现。
三、解决方案
public class SingletonUser {
//增加volatile修饰对象
private static volatile SingletonUser instance = null;
private SingletonUser() {}
public SingletonUser getInstance(){
if(instance ==null){
synchronized (SingletonUser.class){
if(instance ==null){
instance = new SingletonUser();
}
}
}
return instance;
}
}