今天学习的这个模式比较简单,就是常说的单例模式。其实在看《Head First 设计模式》这本书之前我就会用单利模式,但是不能没有深入理解单利模式。可能在看到这篇文章的时候你也会单利模式,但是我下面讲到的知识点你也可能不知道哟。话不多说先来一段单例模式的代码
1 下面这段代码是我常写的单例模式。我说说下面这段代码的有点和缺点吧。
优点:只有在用到单实例的时候才会新建对象。换句话说,如果你的程序中可能用不到这个单实例对象,那么你的程序就不会创建这个对象,如果这个单实例对象是很耗费资源的对象,那么这样的单实例是很节约资源的。
缺点:在多线程中,下面的单实例可能会创建出多个对象,假如 某时刻singleton为null 这时有两个线程分别为 线程1 和线程2 ,线程1进入getInstance()方法,判断singleton为null,线程一准备创建singleton对象,单还没有创建。此时线程二进入getInstance()方法判断singleton对象也为空,也准备创建对象(但还没有创建)。此时线程1又得到资源,创建好singleton(假设该singleton为singleton1)后返回对象singleton1。然后线程2得到资源继续创建对象singleton(假设该singleton为singleton2)后返回对象singleton2.那么系统将会得到两个对象。你设计的是单实例,只希望得到一个对象,但是系统可能会产生两个对象。这可能给系统带来灾难性后果。
public class Singleton {
private static Singleton singleton;
private Singleton(){
}
public static Singleton getInstance(){
if(null == singleton){
singleton = new Singleton();
}
return singleton;
}
}
2 为了解决上面单例模式带来的问题,我们提出了另外一种单实例的写法。但是这种写法与第一种方法优缺点是互逆的。程序一编译就会产生一个单实例对象。万一这个对象很费资源并且我们系统可能有用不到者个对象呢?,不过下面这种写法确实是解决了多线程出现创建多个线程的问题。因为在静态初始化器中创建单例,确保了线程安全。系统只会创建一个单实例对象。
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return singleton;
}
}
public class Singleton {
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;
}
}
需要说明的是 singleton必须要使用volatile 修饰.原因如下:
在 singleton = new Singleton();创建对象过程,实例化一个对象要分为三个步骤:
分配内存空间
初始化对象
将内存空间的地址赋值给对应的引用
如果不使用volatile修饰singleton,那么由于编译器执行的指令重排序2、3的顺序可能发生变化,即先3 后2 那么存在这种情况:
线程A 调用getinstance方法执行singleton = new singleton ()的时候,参考上面的创建对象的过程,可能存在这种情况singleton != null (因为执行了3 ),但是由于2还未执行,singleton还未实例化完成,即是一个不完整的对象。
此时线程B访问getinstance()方法,并返回了这个不完整的singleton,那么在调用singleton时就可能会出错(因为其还未初始化完全)
如果使用volatile修饰singleton则告诉编译器不要对 singleton的创建过程做指令重排序的操作,既不会出现 singleton != null并且singleton还未初始化完全的情况。因此volatile修饰的singleton创建出来是安全的。
4 虽然第三条看上去已经解决了所有的问题,但是,JSR133并不推荐我们使用这样的单例,详见
Does the new memory model fix the "double-checked locking" problem?
在这里JSR133推荐了一种更好的单例模式的写法
如果有同学觉得下面的代码与 2 中的方式有相同之处,请参看周志明写的《深入理解java虚拟机》的第七章,了解虚拟机类加载机制就会明白下面代码与2中代码的区别。
public class Singleton {
private Singleton(){}
private static class SingletonHolder{
static Singleton SINGLETONHOLDER = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.SINGLETONHOLDER;
}
}
JSR133给出的理由是:
在新的内存模型下,使用volatile的性能成本上升,几乎达到同步成本的水平。因此,仍然没有充分的理由使用双重检查锁定(DCL)来写单实例