单例模式中的懒汉模式大家都很熟悉,我们看一下下面的代码是否有问题:
public class Singleton {
private static Singleton singleton;
private Singleton(){ }
public static Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
上面的写法是错误的,问题出在它无法保证线程的安全性,我们将其改写一下:
public class Singleton {
private static Singleton singleton;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
可以看到我们在getInstance()
方法上加上了synchronized
关键字,使其成为了同步方法,但是这样写程序效率就下降了,那有没有可以优化的方法呢? 聪明的小伙伴想到了双重检查DCL,再次进行改写:
public class Singleton {
private static Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if(singleton == null){ // 1
synchronized (Singleton.class){ // 2
if(singleton == null){ // 3
singleton = new Singleton(); // 4
}
}
}
return singleton;
}
}
这样修改之后看似没有问题了,但却是有隐患的,那么问题出在哪里呢?在分析问题之前我们先回顾一下创建对象的过程,实例化一个对象大致分为三个过程:
①. 分配内存空间
②. 初始化对象
③. 将内存空间的地址赋值给对应的引用
但是有些编译期或者处理器为了提升处理效率,会对指令进行重排序,步骤②、③的顺序可能会发生改变,变成如下顺序:
①. 分配内存空间
②. 将内存空间的地址赋值给对应的引用
③. 初始化对象
如果②、③发生了重排序就会导致第二个判断会出错,singleton != null,但是它其实仅仅只是一个地址而已,此时对象还没有被初始化,所以return的singleton对象是一个没有被初始化的对象,如下图所示:
当线程A先于线程B获取Singleton
类锁,并且执行到步骤4,即singleton分配了内存地址,而线程B 刚好执行到1,此时if(singleton == null)的条件是不成立的,这时候B取到的对象就是初始化未完成的对象。针对这个问题有如下两种解决办法:
1.禁止初始化阶段步骤② 、③发生重排序。
2.允许初始化阶段步骤2 、3发生重排序,但是不允许其他线程“看到”这个重排序。
一、基于volatile
的解决方案:
volatile
关键字有禁止指令重拍序的内存语义,所有的写(write)操作都将发生在读(read)操作之前。因此我们可以把singleton变量声明为volatile
类型的。如下:
public class Singleton {
//使用volatile关键字确保该变量对所有线程的可见性,所有对其的写操作先于对其读操作
private volatile static Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if(singleton == null){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
二、基于类初始化的解决方案:
利用classloder的机制来确保只有一个线程初始化instance。JVM在类初始化阶段会获取一个锁,这个锁可以同步多个线程对同一个类的初始化。实质上就是不允许其他线程可见。
public class Singleton {
private static class SingletonHolder{
public static Singleton singleton = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.singleton;
}
}
参考http://cmsblogs.com/?p=2161