题目:设计一个类,我们只能生产该类的一个实例。
C#版本:
- 思路一:只适用于单线程环境
public sealed class Singleton1{
private Singleton1(){}//构造函数设为私有函数以禁止他人创建实例
private static Singleton1 instance = null;//静态实例
public static Singleton1 Instance{
get{
if (instance == null){
instance = new Singleton1();//在需要的时候创建该实例
}
return instance;
}
}
}
上述代码存在的问题:两个线程同时运行到判断instance是否为null的if语句,并且instance的确没有创建时,那么两个线程都会创建一个实例,此时类型Singleton1就不再满足单例模式的要求。
- 思路二:可在多线程环境中工作,但效率不高。
public sealed class Singleton2{
private Singleton2(){}
private static readonly object syncObj = new object();
private static Singleton2 instance = null;
public static Singleton2 Instance{
get{
lock(syncObj){//同步锁
if (instance == null){
instance = new Singleton2();
}
}
return instance;
}
}
}
上述代码缺点:每次通过属性Instance得到Singleton2的实例都会试图加上一个同步锁,而加锁是一个非常耗时的操作。
- 思路三:加同步锁前后两次判断实例是否已存在
public sealed class Singleton2{
private Singleton2(){}
private static object syncObj = new object();
private static Singleton3 instance = null;
public static Singleton3 Instance{
get{
if (instance == null){//只在instance没有创建时加锁
lock(syncObj){
if (instance == null){
instance = new Singleton3();
}
}
}
return instance;
}
}
}
上述代码缺点:用两个if,代码复杂。
- 强烈推荐解法一:利用静态构造函数
public sealed class Singleton4{
private Singleton4(){}
/*
在初始化静态变量instance的时候创建实例。由于C#在调用静态构造函数时初始化静态变量,.NET运行时能够确保只调用一次静态构造函数,这样就能够保证只初始化一次instance*/
private static Singleton4 instance = new Singleton4();
public static Singleton4 Instance{
get{
return instance;
}
}
}
上述代码的缺点:C#中调用静态构造函数的时机不是由程序员掌控的,而是当.NET运行时第一次使用一个类型的时候自动调用该类型的静态构造函数。因此在Singleton4中,实例instance并不是第一次调用属性Singleton.Instance的时候创建,而是在第一次用到Singleton4的时候就会被创建。假设我们在Singleton4中添加一个静态方法,调用该静态函数时不需要创建一个实例的,但如果按照Singleton4的方法实现单例模式,则仍然会过早地创建实例,从而降低内存的使用效率。
- 强烈推荐解法二:实现按需创建实例
public sealed class Singleton5{
Singleton5(){}
public static Singleton5 Instance{
get{
return Nested.instance;
}
}
class Nested{//内部私有类型
static Nested(){}
internal static readonly Singleton5 instance = new Singleton5();
}
}
当第一次用到嵌套类型Nested的时候,会调用静态构造函数创建Singleton5 的实例instance。类型Nested只在属性Singleton5.Instance中被用到,由于其私有属性他人无法使用Nested类型。因此当我们第一次试图通过属性Singleton5.Instance得到Singleton5的实例时,会自动调用Nested的静态构造函数创建实例instance。如果我们不调用属性Singleton5.Instance,那么久不会触发.NET运行时调用Nested,也不会创建实例,这样就真正做到了按需创建。
C++版:
- 第一版:保证只产生一个对象。对GetInstance稍加修改,这个设计模板便可以适用于可变多实例情况,如一个类允许最多五个实例。
class CSingleton
{
private:
CSingleton() //构造函数是私有的
{
}
static CSingleton *m_pInstance;
public:
static CSingleton * GetInstance()
{
if(m_pInstance == NULL) //判断是否第一次调用
m_pInstance = new CSingleton();
return m_pInstance;
}
};
- 第二版:解决参数传递问题和赋值问题。
class CSingleton
{
private:
CSingleton() //构造函数是私有的
{
}
CSingleton(const Singleton& s){}
CSingleton& operator=(const CSingleton& s){}
static CSingleton *m_pInstance;
public:
static CSingleton * GetInstance()
{
if(m_pInstance == NULL) //判断是否第一次调用
m_pInstance = new CSingleton();
return m_pInstance;
}
};
- 第三版:解决内存释放
- 方案1:程序在结束的时候,系统会自动析构所有的全局变量。事实上,系统也会析构所有的类的静态成员变量,就像这些静态成员也是全局变量一样。利用这个特征,我们可以在单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例。如下面的代码中的CGarbo类(Garbo意为垃圾工人)
class CSingleton
{
private:
CSingleton()
{
}
static CSingleton *m_pInstance;
class CGarbo //它的唯一工作就是在析构函数中删除CSingleton的实例
{
public:
~CGarbo()
{
if(CSingleton::m_pInstance)
delete CSingleton::m_pInstance;
}
};
static CGarbo Garbo; //定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数
public:
static CSingleton * GetInstance()
{
if(m_pInstance == NULL) //判断是否第一次调用
m_pInstance = new CSingleton();
return m_pInstance;
}
};
- 方案2:
class CSingleton
{
private:
CSingleton() //构造函数是私有的
{
}
CSingleton(const CSingleton &);
CSingleton & operator = (const CSingleton &);
public:
static CSingleton & GetInstance()
{
static CSingleton instance; //局部静态变量
return instance;
}
};
- 解决线程安全问题:
class Lock
{
private:
CCriticalSection m_cs;
public:
Lock(CCriticalSection cs) : m_cs(cs)
{
m_cs.Lock();
}
~Lock()
{
m_cs.Unlock();
}
};
class Singleton
{
private:
Singleton();
Singleton(const Singleton &);
Singleton& operator = (const Singleton &);
public:
static Singleton *Instantialize();
static Singleton *pInstance;
static CCriticalSection cs;
};
Singleton* Singleton::pInstance = 0;
Singleton* Singleton::Instantialize()
{
if(pInstance == NULL)
{ //double check
Lock lock(cs); //用lock实现线程安全,用资源管理类,实现异常安全
//使用资源管理类,在抛出异常的时候,资源管理类对象会被析构,析构总是发生的无论是因为异常抛出还是语句块结束。
if(pInstance == NULL)
{
pInstance = new Singleton();
}
}
return pInstance;
}
扩展问题:
- 定义只能在堆上定义的对象
- 定义只能在栈上定义的对象
解决方法见:如何限制对象只能建立在堆上或者栈上