一、单例模式定义:
保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
二、应用场景:
比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。其他还有如系统的日志输出、MODEM的联接需要一条且只需要一条电话线,操作系统只能有一个窗口管理器,一台PC连一个键盘等等。
三、方式:
根据单例对象创建时间,可分为两种模式:饿汉模式 + 懒汉模式。
懒汉模式:指全局的单例实例在第一次被使用时构建。
饿汉模式:指全局的单例实例在类装载时构建。
四、代码实现
1.懒汉模式
class Singleton {
private:
Singleton() {} //构造函数是私有的
static Singleton *instance;
public:
static Singleton * GetInstance() {
if(instance == nullptr) //判断是否第一次调用
instance = new Singleton();
return instance;
}
};
Singleton * Singleton::instance = nullptr;
单例类Singleton有以下特征:
它有一个指向唯一实例的静态指针instance,并且是私有的;
它有一个公有的函数,可以获取这个唯一的实例,并且在需要的时候创建该实例;
它的构造函数是私有的,这样就不能从别处创建该类的实例。
2.懒汉模式+对象释放
程序在结束的时候,系统会自动析构所有的全局变量。事实上,系统也会析构所有的类的静态成员变量,就像这些静态成员也是全局变量一样。利用这个特征,我们可以在单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例。如下面的代码中的Garbo类(Garbo意为垃圾工人):
class Singleton {
private:
Singleton() {}
static Singleton *m_pInstance;
class Garbo { //它的唯一工作就是在析构函数中删除Singleton的实例
public:
~Garbo() {
if(Singleton::m_pInstance)
delete Singleton::m_pInstance;
}
};
static Garbo garbo; //定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数
public:
static Singleton * GetInstance() {
if(m_pInstance == nullptr) //判断是否第一次调用
m_pInstance = new Singleton();
return m_pInstance;
}
};
Singleton * Singleton::m_pInstance = nullptr;
类Garbo被定义为Singleton的私有内嵌类,以防该类被在其他地方滥用。
程序运行结束时,系统会调用Singleton的静态成员garbo的析构函数,该析构函数会删除单例的唯一实例。
使用这种方法释放单例对象有以下特征:
在单例类内部定义专有的嵌套类;
在单例类内定义私有的专门用于释放的静态成员;
利用程序在结束时析构全局变量的特性,选择最终的释放时机;
使用单例的代码不需要任何操作,不必关心对象的释放。
3.饿汉式
使用局部静态变量,非常强大的方法,完全实现了单例的特性,而且代码量更少,也不用担心单例销毁的问题。
class Singleton {
private:
Singleton() {} //构造函数是私有的
public:
static Singleton & getInstance() {
static Singleton instance = new Singleton(); //局部静态变量
return instance;
}
};
但是当以如下方法使用单例时问题来了:
Singleton singleton = Singleton::GetInstance();
这么做就出现了一个类拷贝的问题,这就违背了单例的特性。产生这个问题原因在于:编译器会为类生成一个默认的构造函数,来支持类的拷贝。
我们要禁止类拷贝和类赋值,禁止程序员用这种方式来使用单例:
class Singleton {
private:
Singleton() {} //构造函数是私有的
Singleton(const Singleton &);
Singleton & operator = (const Singleton &);
public:
static Singleton & GetInstance() {
static Singleton instance = new Singleton(); //局部静态变量
return instance;
}
};
4.线程安全——双重检测锁定(Double Checked Locking)
class Singleton {
private:
Singleton() {} //构造函数是私有的
static Singleton *instance;
public:
static Singleton * GetInstance() {
if(instance == nullptr) { //判断是否第一次调用
lock();
if(instance == nullptr)
instance = new Singleton();
unlock();
}
return instance;
}
};
Singleton * Singleton::instance = nullptr;
DCL用于在多线程环境下保证单一创建Singleton对象。第一次check不用加锁,但是第二次check和创建对象必须加锁。由于编译器可能会优化代码,乱序执行,可能导致DCL失效。例如:
m_Instance = new Singleton(); 这个语句会分成三步完成:
(1)分配内存,
(2)在已经分配的内存上调用构造函数创建对象,
(3)将对象赋值给指针m_Instance .
但是这个顺序很可能会被改变为1,3,2。如果A线程在1,3执行完后,B线程执行第一个条件判断if(m_Instance ==0),此时锁不能起到保护作用。B线程会认为m_Instance 已经指向有效对象,可以去使用了。嘿嘿,灾难发生。
volatile对于执行顺序也没有帮助,解决不了DCL的问题。
5.线程安全——pthread_once
template
class Singleton : boost::noncopyable {
public:
static T& getInstance() {
pthread_once(&ponce_, &Singleton::init);
return *value_;
}
private:
Singleton();
~Singleton();
static void init() {
value_ = new T();
}
private:
static pthread_once_t ponce_;
static T* value_;
};
template
pthread_once_t Singleton::ponce_ = PTHREAD_ONCE_INIT;
template
T * Singleton::value_ = nullptr;
参考《Linux多线程服务端编程》陈硕 2.5节。
使用pthread_once_t来保证线程安全。线程安全性由Pthreads库保证。
没有考虑对象的销毁,在长时间运行的服务器程序里,这不是一个问题,反正进程也不打算正常退出。在短期运行的程序中,程序退出时自然就释放所有资源了。