参考:http://blog.csdn.net/liushuijinger/article/details/9069801 //单例模式介绍的文章
http://www.cnblogs.com/hemingwang0902/archive/2011/12/29/2306263.html //关于枚举的文章
第一篇文章中说到的简单懒汉式就是用判空的方式使不会实例化多次。但是在多线程的时候是不安全的。应该在线程A和线程B都进入实例化时,会有两个实例。
我们可以加同步锁。但我们发现只加一个同步锁会导致getInstance()每次都要请求同步锁,效率低,所以一般会在同步锁外面再加一层判空,这样当不是第一次进入的时候就不能再请求同步锁从而提高效率了。
加同步锁的懒汉式似乎能保证安全,但是由于Java无序写(out-of-order-writes)的特性。instance=new instance();实际上是jvm会有两个步骤,一个是给instance的实例申请内存空间,第二个是通过构造方法实例化。所以在分配好空间后,instance就不是null了,毕竟它作为一个指针已经指向了一个内存地址了。如果这时候线程B做if(instance==null)的判断会得到不为空的结果,这样B使用的instance就会是空的,导致空指针异常。
借用下文章中的一个例子
//双重锁定懒汉式
public class Singleton {
//单例实例变量
private static Singleton instance = null;
//私有化的构造方法,保证外部的类不能通过构造器来实例化
private Singleton() {}
//获取单例对象实例
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
System.out.println("我是双重锁定懒汉式单例!");
return instance;
}
}
用这个列子就可以解决线程安全的问题了。因为这里我们不是instance = new Singleton(); 而是temp=new Singleton();它们的差别是这时候就是实例化的过程没完成线程B而开始判空,由于temp不为空,但instance还是空。所以线程B只能请求同步锁但A在使用,所以B只能等待。当A执行到instance=temp;这里不需要实例化
只是将isntance指向temp所指的内存地址。这样也就不存在无序写对我们造成影响了。
再借用下文章对无序写的介绍
mem = allocate(); //为Singleton对象分配内存。
instance = mem; //注意现在instance是非空的,但是还没有被初始化。
ctorSingleton(instance); //调用Singleton的构造函数,传递instance.
从文章的评论中我们了解到还有一个用枚举的方法来实现这个单例模式的方法。枚举不太懂,下次了解过了记得补充。