先说答案,不靠谱!
一般说,单例模式加载的时候,使用了饿汉式,类一加载就初始化;使用了懒汉式,调用的时候才初始化。
懒汉式,一般又有几种线程安全的方式。其中有一种就很著名:双重锁检查。
至于由来,百度就好了~
上代码:
public class Test{
private static Singleton singleton;
public static Singleton getSingleton() {
//如果第一个线程获取到了单例的实例对象, 后面的线程再获取实例的时候不需要进入同步代码块中了
if (singleton == null) {
// 同步代码块用的锁是单例的字节码文件对象,且只能用这个锁
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
很正常的双重锁检查的单例模式代码,但问题出在哪儿了呢?
再看怎么执行:
public class Test{ //1
private static Singleton singleton; //2
public static Singleton getSingleton() { //3
//如果第一个线程获取到了单例的实例对象,
//后面的线程再获取实例的时候不需要进入同步代码块中了
if (singleton == null) { //4 第一次检查
// 同步代码块用的锁是单例的字节码文件对象,且只能用这个锁
synchronized (Singleton.class) { //5 加锁
if (singleton == null) { //6 第二次检查
singleton = new Singleton(); //7 问题出现了!
} //8
} //9
} //10
return singleton; //11
}
}
诺,执行到第7行的时候,就是创建对象,singleton = new Singleton(); 这一行可以分解为如下的3行伪代码:
memory = allocate(); //1,分配对象的内存空间
ctorInstance(memory); //2,初始化对象
instance = memory; //3,设置instance指向刚分配的内存地址
以上三行代码,2,3步可能会被重排序,如果重排序之后,发生的情况如下:
memory = allocate(); //1,分配对象的内存空间
instance = memory; //3,设置instance指向刚分配的内存地址;注意!此时对象还没有被初始化!
ctorInstance(memory); //2,初始化对象
重排序之后的执行顺序:
这时候就找到问题了,如果发生重排,线程B有可能在第4行判断singleton不为null,之后线程B开始访问singleton引用的对象,而此时这个对象还没有被A线程初始化。这不就出问题了?
知道了问题,怎么解决是接下来的问题,思路如下:
1,不允许2,3步重排序
2,即使2,3重排序,那这个过程不让其他线程知晓,对外显示为原子操作。
这两种思路,产生了两种解决方式,分别为:volatile,类初始化;
欲知后事如何,且听下回分解~撸代码去了。
备注:以上部分图片来自《Java并发编程的艺术》,如有侵权,请告知,必删。