首页 > 代码库 > 设计模式之---单例模式

设计模式之---单例模式

设计模式的世界中,单例模式可能是最简单的一种模式,虽说简单,但想要彻底的弄明白它,还是要经历一点点的波折。

下面我为大家慢慢道来:


在实际开发中,对于有些对象,我们只需要一个,比如线程池(thread pool),缓存(cache),对话框,日志对象,任务管理器等等。这些对象只能有一个实例,如果出现多个实例,则会导致许多问题的产生。


你可能会说,我们为什么不用java的全局变量,多方便的。是的,但是这样做是有缺点的,如果我们将一个对象赋值给一个全局变量,那么我们在程序一开始就必须创建好对象,一旦这个对象非常耗费资源,而程序在执行中又一直没有用到它,那么这样是非常的浪费。


单例模式,要保证一个对象只能被实例化一次,我们先来看一段雏形代码:

public class Singleton {
	private static Singleton uniqueInstance;
	private Singleton(){}
	
	public static Singleton getInstance(){
		if(uniqueInstance==null){
			uniqueInstance = new Singleton();
		}
		return uniqueInstance;
	}
}

在这里,我们利用一个静态变量来记录Singleton类的唯一实例,并把构造方法设置为私有,那么只有在Singleton类内部才可以调用此构造方法。然后我们用getInstance()方法实例化对象,并返回这个实例。如果uniqueInstance是空的,表示还没有创建实例,如果不为空,表示之前已经创建过对象,则直接跳过至return语句。


但是仔细想想,就会发现,上面的代码是有些问题的,问题在哪里呢?


如果我们想应用多线程,那么如果有两个或两个以上的线程要执行上面这段代码,则可能会出现产生多个实例对象的情况,也就是说,没有同步机制,可能两个线程都执行到了uniqueInstance==null的情况,那么之后就会产生两个object,而这样的结果就是不对的。


这是你可能会说,给方法加synchronized关键字啊,是的,于是有了下面的代码:

public class Singleton {
	private static Singleton uniqueInstance;
	private Singleton(){}
	
	public static synchronized Singleton getInstance(){
		if(uniqueInstance==null){
			uniqueInstance = new Singleton();
		}
		return uniqueInstance;
	}
}

这样,我们通过增加synchronized关键字到getInstance()方法中,可以使得每个线程在进入这个方法之前,要先等候别的线程离开该方法。也就是说不会有两个线程可以同时进入该方法。


但是这样做的问题是什么?仔细想想,很简单,那就是:同步会降低性能


也就是说,我们只有第一次执行此方法的时候才需要同步,一旦设置好了uniqueInstance变量,就不再需要同步这个方法了,因为只有第一次unqueInstance==null,之后每次调用这个方法,同步都是多余的,甚至是一种累赘。


所以,有时候要根据情况灵活的应对。


如果getInstance()的性能对应用程序不是很重要,那就什么都别做,但是必须要知道的是,同步一个方法可能会造成程序执行的效率下降100倍,所以很多时候,我们就要重新考虑了。


当然,如果我们有时很急切的需要创建实例,而不用延迟实例化的做法,就是在静态初始化器中创建单例,如下面的代码:

public class Singleton {
	private static Singleton uniqueInstance=new Singleton();
	private Singleton(){}
	
	public static synchronized Singleton getInstance(){
		return uniqueInstance;
	}
}

而且这段代码是线程安全的。


下面我们来看单例模式的最终版:

双重检查加锁,在getInstance()中减少使用同步:

利用双重检查加锁(double-checked locking),首先检查是否实例已经创建了,如果未创建,才进行同步,这样,只有第一次会同步,而这正是我们想要的:

public class Singleton {
	private volatile static Singleton uniqueInstance;
	private Singleton(){}
	
	public static Singleton getInstance(){
		if(uniqueInstance==null){
			synchronized (Singleton.class){
				if(uniqueInstance==null){
					uniqueInstance = new Singleton();
				}
			}
		}
		return uniqueInstance;
	}
}

volatile关键字,用来确保将变量的更新操作通知到其他线程,保证新值能立即同步到主内存,以及每次使用前立即从主内存刷新。当把变量声明为volatile后,编译器与运行时都会注意到这个变量是共享的。

在这里,volatile关键字确保了,当uniqueInstance变量被初始化成Singleton实例时,多个线程能正确的处理uniqueInstance变量。

如果性能是你关系的重点,那么这个做法可以帮你大大减少getInstance()的时间耗费。

设计模式之---单例模式