所谓单例模式,顾名思义即只存在一个全局的实例,相对来说是应该最简单的一种设计模式吧。但是虽然单例模式简单,但却有好几种不同的实现方式,这些将在下面提到,现在先亮出单例模式的UML类图。
单例模式有饿汉模式和懒汉模式,这两种的差别主要在对象的创建时机,相同的是一个静态的可供外部获取这个唯一实例对象的接口,一个静态的内部对象指针以及一个内部的构造函数。如下
public:
static singleton *getInstance();
private:
singleton();
static singleton *m_pInstance;
之所以把m_pInstance声明为内部的静态指针,是为了保证唯一的实例对象,因为所有static是所有该类对象共享的。内部的构造函数是为了不让外部对该对象进行实例化,方便控制。getInstance()不用多说了,只有声明为static才能在不指定具体实例的情况下去获取实例。另外,由于静态变量存储在全局区,所以系统会对内存进行回收。
那么所谓饿汉模式,即程序运行时即创建一个对象,完整代码如下:
class singleton
{
public:
static singleton *getInstance();
private:
singleton();
static singleton *m_pInstance;
};
singleton *singleton::m_pInstance = new singleton();
懒汉模式即需要的时候再创建一个对象,完整代码如下:
class singleton
{
public:
static singleton *getInstance()
{
if(nullptr == m_pInstance)
m_pInstance = new singleton;
return m_pInstance;
}
private:
singleton();
static singleton *m_pInstance;
};
当然这种做法是非线程安全的,试想,当多个线程同时访问getInstance()时,就有可能创建多个实例(尽管只有一个指针)。很简单,既然线程不安全的话就加锁嘛,于是写下了这样的代码(lock()和unlock()都是瞎写的):
static singleton *getInstance()
{
lock();
if(nullptr == m_pInstance)
m_pInstance = new singleton;
unlock();
return m_pInstance;
}
static singleton *getInstance()
{
if(nullptr == m_pInstance)
{
lock();
m_pInstance = new singleton;
unlock();
}
return m_pInstance;
}
首先,对于第一种写法,每次进入函数都要加锁解锁,开销无疑是巨大且没必要的。因为仅仅在没有实例的时候才会出现线程不安全的情况。对于第二种写法,因为加锁的位置不对,仍存在线程不安全的情况。
实际上线程安全的写法可以这样写:
static singleton* getInstance()
{
if(nullptr == m_pInstance)
{
lock();
if(nullptr == m_pInstance)
m_pInstance = new singleton;
unlock();
}
return m_pInstance;
}
windows API及linux下也提供了一些函数,可以实现线程安全,以下仅有linux下的代码,Windows API对应的函数为(msdn进不去,忘了是啥了,改天更新上来吧),使用方法相似:
//linux
class singleton
{
private:
singleton();
static void init()
{
m_pInstance = new singleton;
}
static singleton *m_pInstance;
static pthread_once_t m_once;
public:
static singleton* getInstance()
{
pthread_once(&m_once, init);
return m_pInstance;
}
}
singleton *singleton::m_pInstance = nullptr;
pthread_once_t singleton::m_once = PTHREAD_ONCE_INIT;