[设计模式(二)]Singleton单例模式
Singleton单例模式
使用对象时最基本的事情就是要首先创建对象的实例,一般情况下,这是一件极其简单的事情,就是用new运算符来创建某个对象的实例,简单的人们都不想提及。然而有时候你会发现创建对象的实例也不是那么简单。
比如有一些类,在整个程序运行期间只允许一个实例,或者我们说如果有多个实例存在程序逻辑会变得难以控制,很可能遭遇错误,比如对程序所需要的资源如连接池之类的做集中管理。
或者从逻辑上讲,在整个程序运行期间可以只有一个实例,当然也可以创建多个实例,这对程序逻辑没有影响,但是实施强制单一对象实例的代价要远低于带来的收益。
全部强制对象单一机制有些多余,我们要比较实施强制措施的代价,而不同的实施方案代价和收益是不同的。
最原始方案的就是全局变量,程序开始运行时初始化这个全局变量,代码中每一个使用该类的地方都引用这个全局变量。
全局变量一个缺点是有些对象实例实际上很可能根本不会用到,而初始化又恰恰要花点时间。没有办法做到延迟加载,当然有聪明的人会说一样可以实现延迟加载,设置一个函数,每次通过这个函数来获得这个全局变量的引用,在函数里面会判断该全局变量的状态,如果没有初始化才作初始化的动作。实际上根本的缺点在于当这样的情况变得越来越多的时候,逻辑变得很复杂,代码变得难以控制,职责变得很不明确。
那么我们开始重构,将类的单一实例以及类初始化的职责封装起来,然后提供一个全局的访问点让其他的代码能够访问到这个单一的类的实例。简单的实现方式可能如下:
public sealed class Singleton
{
static Singleton instance = null;
private Singleton () {}
public static Singleton Instance()
{
if (instance==null) {instance = new Singleton (); }
return instance;
}
}
这里把Singleton类的构造函数设置为private是为了其他类不能直接用new方法来创建多个Singleton的实例并使用它,而是提供了一个全局的访问接口Instance()方法,其他的代码只能通过访问Instance方法来获得一个Singleton类的实例。在Instance()中会判断instance是否已经创建,如果没有创建的话才去创建一个实例出来。
练习题:为什么instance要声明为static类型的变量。
简单测试之后发现可以Singleton类这样的设计实现了我们的意图,那么我们再检查Singleton的代码
如果多个线程同时调用Instance()方法,而这时instance还没有被创建,这时会怎么样呢,怎样保持线程同步?
我们可以对Singleton做一点改造,用lock来强制线程同步
public sealed class SingletonLock
{
static SingletonLock instance=null;
static readonly object padlock = new object();
private SingletonLock() {}
public static SingletonLock Instance()
{
lock (padlock)
{
if (instance==null) {instance = new SingletonLock();}
return instance;
}
}
}
每次强制线程同步是很耗费资源的,而实际上只有第一次访问Instance()才需要强制线程同步以确保只创建一个实例,那么可以在锁定线程之前先判断instance是否为null,如果为null才调用lock锁定线程。
public static SingletonLock Instance()
{
if(instance==null)
{
lock (padlock)
{
if (instance==null)
{
instance = new SingletonLock();
}
}
}
return instance;
}
java和c#为Singleton模式提供了天然的支持,于是我们有了更简单的实现方法。
public class Singleton
{
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton Instance()
{ return instance; }
}
static Singleton instance的static关键字确保instance在被载入AppDomain时被初始化,而且不会有多线程的困扰。这样在访问Instance()方法时,instance必定是已经创建好的,这样的代价就是失去了延迟加载的特性。
应该是the static field initializers are executed at an implementation-dependent time prior to the first use of a static field of that class.
应用Singleton模式会有哪些代价呢,首先实现Singleton模式类的Singleton特性不能被子类天然继承,因为私有变量instance和公共方法Instance()都是静态的,而且基类的构造函数是private的。另外一点就是不透明,调用者知道自己在使用一个Singleton类,需要通过Instance()方法来获得实例。
在《敏捷软件开发 原则、模式与实践》中介绍了Monostate模式,Monostate模式使用静态变量来实现多个实例共享数据,这样多个类的实例使用起来像一个实例一样,Monostate的代价一方面在于多次创建对象并且销毁会有性能方面的损失,一方面将一个类改造成遵循Monostate模式的类比较麻烦。
Singleton强制结构单一,防止创造出多个对象实例,Monostate强制行为单一,所有的实例表现得像一个对象。
练习题:
1:
以前论坛经常有人争论全是静态函数的Library和遵循Singleton模式的Library孰优孰劣,静态函数的Library有什么缺点?
2:
CreateSingleton()中instance.GetResource()的返回值分别是什么
public sealed class Singleton
{
static Singleton instance = null;
private int sequence = 0;
private Singleton()
{
}
public static Singleton Instance()
{
if (instance==null)
{
instance = Initialize();
}
return instance;
}
public int GetResource()
{
CreateSequence();
return sequence;
}
private static Singleton Initialize()
{
System.Threading.Thread.Sleep(200);
return new Singleton();
}
private void CreateSequence()
{
sequence = sequence + 1;
}
}
public void TestSingleton ()
{
ThreadPool.QueueUserWorkItem(new WaitCallback(CreateSingleton));
ThreadPool.QueueUserWorkItem(new WaitCallback(CreateSingleton));
}
public void CreateSingleton(object o)
{
SingletonNotThreadSafeConsuming instance = SingletonNotThreadSafeConsuming.Instance();
instance.GetResource();
}