模式原理
确保一个类只有一个实例,并提供一个全局的访问点。
简单来说单利模式需要保证在整个程序的生命周期中,单例类只存在一个可访问的的实例。这样的应用场景有很多,如线程池或数据库连接池在程序中通常只允许存在一份实例,这时就可以通过单利模式来保证。
为了保证一份实例,单利类内部构造函数以及析构函数都作为私有成员,此时只有一个getter接口暴露出来供用户调用。
对于一开始就生成一个静态实例和在有需要的情况下再去申请内存又可分为两种情况,直接在静态区初始化instance,然后通过getInstance返回,这种就被称为饿汉式单例类。也有些写法是在getInstance中new instance然后返回,这种就被称为懒汉式单例类,但这涉及到第一次getInstance的一个判断问题。
懒汉式是以时间换空间的方式。;
饿汉式是以空间换时间的方式。
UML类图
实现代码
饿汉式实现
/********sigle.cpp*********/
class singleton
{
protected:
singleton()
{}
private:
static singleton* p;
public:
static singleton* initance();
};
/********sigle.h*********/
singleton* singleton::p = new singleton;
singleton* singleton::initance()
{
return p;
}
这是饿汉式比较经典的一种实现,由于实在编译阶段初始化静态实例,所以能保证线程安全。
/********sigle.cpp*********/
class singleton
{
protected:
singleton()
{}
private:
static singleton* p;
class CGarbo //它的唯一工作就是在析构函数中删除CSingleton的实例
{
public:
~CGarbo()
{
delete CSingleton::m_pInstance;
}
};
static CGarbo Garbo; //定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数
public:
static singleton* initance();
};
/********sigle.h*********/
singleton* singleton::p = new singleton;
singleton* singleton::initance()
{
return p;
}
我们知道,程序在结束的时候,系统会自动析构所有的全局变量。事实上,系统也会析构所有的类的静态成员变量,就像这些静态成员也是全局变量一样。利用这个特征,我们可以在单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例。
懒汉式实现
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()
{
}
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;
}
};
加垃圾回收机制
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;
}
线程安全的实现方式
总结
单例模式通过类本身来管理其唯一实例,这种特性提供了解决问题的方法。唯一的实例是类的一个普通对象,但设计这个类时,让它只能创建一个实例并提供对此实例的全局访问。唯一实例类Singleton在静态成员函数中隐藏创建实例的操作。习惯上把这个成员函数叫做Instance(),它的返回值是唯一实例的指针。
懒汉与饿汉
单例大约有两种实现方法:懒汉与饿汉。
懒汉:故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化,所以上边的经典方法被归为懒汉实现;
饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。
特点与选择:
由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。
在访问量较小时,采用懒汉实现。这是以时间换空间。线程安全的懒汉实现
线程不安全,怎么办呢?最直观的方法:加锁。