单例模式(Singleton Pattern) —— 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
何为单例模式,在GOF的《设计模式:可复用面向对象软件的基础》中是这样说的:保证一个类只有一个实例,并提供一个访问它的全局访问点。首先,需要保证一个类只有一个实例;在类中,要构造一个实例,就必须调用类的构造函数,如此,为了防止在外部调用类的构造函数而构造实例,需要将构造函数的访问权限标记为protected或private;最后,需要提供要给全局访问点,就需要在类中定义一个static函数,返回在类内部唯一构造的实例。
单例模式的结构是设计模式中最简单的,但是想要完全实现一个线程安全的单例模式还是有很多陷阱的。单例模式的应用场景:有一些对象其实只需要一个,比如:线程池,缓存,对话框,处理偏好设置和注册表的对象,日志对象,充当打印机,显卡等设备的驱动程序对象。这些对象只能够拥有一个实例,如果创建出了多个实例,就会导致一些程序的问题。程序的行为异常,资源使用的过量,或者导致不一致的结果。常用来管理共享的资源,比如数据库的连接或者线程池。
方式一(经典懒汉实现):
#pragma once class Singleton { public: static Singleton* Instance(); ~Singleton(); private: Singleton(); static Singleton* _instance; }; #include "stdafx.h" #include<iostream> #include "Singleton.h" Singleton* Singleton::_instance = nullptr; Singleton::Singleton() { } Singleton * Singleton::Instance() { if(nullptr==_instance) { _instance = new Singleton(); } return _instance; } Singleton::~Singleton() { } #include "stdafx.h" #include"Singleton.h" int main() { Singleton* sgt = Singleton::Instance(); return 0; }这是最简单,也是最普遍的实现方式。但是,这种实现方式,有很多问题,比如:没有考虑到多线程的问题,在多线程的情况下,就可能创建多个Singleton实例。(可以想一下在多线程情况下,产生多个Singleton实例的问题,结合C++对象模型,对象内存布局来思考?) 以下版本是改善的版本。
方式二(线程安全懒汉实现):
#pragma once #include<mutex> class Singleton { public: static Singleton* Instance(); ~Singleton(); private: Singleton(); static Singleton* _instance; static std::mutex _mutex; }; #include "stdafx.h" #include<iostream> #include "Singleton.h" Singleton* Singleton::_instance = nullptr; std::mutex Singleton::_mutex; Singleton::Singleton() { } Singleton * Singleton::Instance() { if(nullptr == _instance) { _mutex.lock(); if(nullptr==_instance) { _instance = new Singleton(); } _mutex.unlock(); } return _instance; } Singleton::~Singleton() { } #include "stdafx.h" #include"Singleton.h" int main() { Singleton* sgt = Singleton::Instance(); return 0; }此处进行了两次_instance == nullptr的判断,使用的所谓的“双检锁”机制。因为进行一次加锁和解锁是需要付出对应的代价的,而进行两次判断,就可以避免多次加锁与解锁操作,同时也保证了线程安全。但是,这种实现方法在平时的项目开发中用的很好,也没有什么问题?但是应当注意加锁操作将成为一个性能的瓶颈;为此,一种新的单例模式的实现也就出现了。
方式三(饿汉实现):
#pragma once class Singleton { public: static Singleton* Instance(); ~Singleton(); private: Singleton(); static Singleton* _instance; }; #include "stdafx.h" #include<iostream> #include "Singleton.h" Singleton* Singleton::_instance = new Singleton(); Singleton::Singleton() { } Singleton * Singleton::Instance() { return _instance; } Singleton::~Singleton() { } #include "stdafx.h" #include"Singleton.h" int main() { Singleton* sgt = Singleton::Instance(); return 0; }因为静态初始化在程序开始时,也就是进入主函数之前,由主线程以单线程方式完成了初始化,所以静态初始化实例保证了线程安全性。在性能要求比较高时,就可以使用这种方式,从而避免频繁的加锁和解锁造成的资源浪费。
方式四(静态局部变量懒汉实现):
#pragma once class Singleton { public: static Singleton* Instance(); ~Singleton(); private: Singleton(); }; #include "stdafx.h" #include<iostream> #include "Singleton.h" Singleton::Singleton() { } Singleton * Singleton::Instance() { static Singleton _instance; return &_instance; } Singleton::~Singleton() { } #include "stdafx.h" #include"Singleton.h" int main() { Singleton* sgt = Singleton::Instance(); return 0; }其实是使用了C++中成员函数的静态变量的特点:静态局部变量在第一次使用时初始化,并不会销毁直到程序退出。
关于懒汉饿汉,可以这样简单粗暴的理解: 懒汉式的特点是延迟加载,比如配置文件,采用懒汉式的方法,顾名思义,懒汉么,很懒的,配置文件的实例直到用到的时候才会加载。 饿汉式的特点是一开始就加载了,如果说懒汉式是“时间换空间”,那么饿汉式就是“空间换时间”,因为一开始就创建了实例,所以每次用到的之后直接返回就好了。
