数学与逻辑学
中,singleton
定义为:有且仅有一个元素的集合。
单例模式最初的定义出现在《设计模式》(艾迪生维斯理,1994):"保证一个类仅有一个实例,并提供一个访问它的全局访问点。"
对于系统中的某些类来说,只有一个实例很重要:
例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。
如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。
因此有时确保系统中某个对象的唯一性即一个类只能有一个实例
非常重要。
根据定义,显然单例模式的要点有三个:
从具体实现角度来说:
根据名字就可以知道:饿汉,
因为饿,所以猴急,巴不得第一时间就把食物送到嘴边;
因此,这个模式的单例模式唯一的实例是在主函数运行之前就产生了的!
根据上述要点,不难实现如下代码:
class Singleton { public: static Singleton *getinstance() { return &instance; } private: static Singleton instance; Singleton() {} Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; }; Singleton Singleton::instance;
通过如下测试代码,我们来看饿汉单例模式的结果:
int main() { Singleton *a1 = Singleton::getinstance(); Singleton *a2 = Singleton::getinstance(); Singleton *a3 = Singleton::getinstance(); cout << a1 << endl; cout << a2 << endl; cout << a3 << endl; return 0; }
根据定义我们知道,a1
、a2
、a3
打印出来应该是同一个地址
!
可以看到,是同一个地址
!
有了上面饿汉式
的经验,我们同样可以这么想:
懒汉式
,因为懒,所以唯一实例能拖就拖;
一直等到有人要使用的时候,才不得不构造
。
同样的,根据定义我们可以写出如下代码:
class Singleton { public: static Singleton *getinstance() { if (instance == nullptr) instance = new Singleton(); return instance; } private: static Singleton *instance; Singleton() {} Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; }; Singleton *Singleton::instance = nullptr;
运行结果:
虽然运行结果没有问题,
但是,这个代码是有问题的:
线程不安全
!
对于if (instance == nullptr)
、instance = new Singleton()
这两句来说,如果处在多线程环境下:
我们会发现这种情况下,会执行多次对象的构造,这和我们的初衷不符!
为了避免上述问题,我们改进代码:
加入线程互斥锁(mutex)
:
std::mutex mtx; class Singleton { public: static Singleton* getinstance() { if(instance == nullptr) { lock_guard<std::mutex> lck(mtx); if(instance == nullptr) { instance = new Singleton(); } } return instance; } private: static Singleton* instance; Singleton(){} Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; }; Singleton* Singleton::instance = nullptr;
注意:
为了避免线程安全
问题:需要进行锁+两次判断
;
避免因为一个线程后续操作未完成,而使得后来的线程进入if语句
。
可以看到运行正确
!
对于懒汉式
来说,还有更精简的一种写法:
class Singleton { public: static Singleton* getinstance() { static Singleton instance; return &instance; } private: Singleton(){} Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; };
运行结果如下:
注意:
这个精简版之所以也是线程安全的,是因为,在底层对于static静态局部变量的初始化
,编译器会自动加锁和解锁。
将上述代码在Linux
通过g++
编译,命令如下:
g++ -o 单例模式 单例模式.cpp -g
:
接下来启动gdb
调试:
gdb 单例模式 -q
:
接下来输入l
:(注意是英文L的小写)
接着下断点:b 10
:
接着运行到断点处:run
:
接下来执行:disassemble getinstance
:
我们可以看到,底层的汇编是自动加锁
、解锁
的!
【1】单例模式(百度百科)