单例模式介绍
目录
引入
单例模式概念
单例模式的设计类型
饿汉式创建对象
懒汉式创建对象
懒汉式的线程安全
懒汉式单例模式所造成的内存问题
引入
设想一下,你现在正在使用编译器写代码,此时你点工具箱,它会显示出来一个功能界面,你再点一下,他就会关闭,不会再显示出另一个界面。如图:
它永远不会是这样:
不论你点多少次,它只会显示一次。这其实就是一个单例模式。
单例模式概念
单例模式是指一个类有且仅有一个实例,并提供一个可以访问它的全局访问点,即:在内存中仅会创建一次对象。
就像上图所示的,我们每次点击工具栏后,弹出的页面都是一样的,那么我们就没必要让它弹出很多次。这里的每一个页面我们可以设想为一个对象,如果我们创建很多个对象,但是它们的作用都是相同的,那这是对内存资源的极大浪费(每次创建对象都会开辟内存)。最好的解决办法就是:我们只创建一个对象,并提供一个可以访问它的方法,让所有需要的函数都共享这一对象。
举例:
单例模式的设计类型
单例模式的类型有两种:
- 懒汉式:在真正需要使用时采取创建该对象。
- 饿汉式:在类加载时已经创建好该对象,等着被调用。
当你肚子不太饿时,你去饭店可以等待一下,让厨师给你现做,可以理解为懒汉式;
当你非常饿时,你提前预约,告诉饭店老板,你现在给我做,我马上过去,等你到达时,菜已经提前做好了,可以理解为饿汉式;
饿汉式创建对象
饿汉式创建对象即:在类加载时,单例对象已经被提前创建好了,当程序调用时,直接返回这个对象即可。
代码实现:
class Singleton{private:int value;static Singleton instance;private:Singleton(int x=0):value(x){}Singleton(const Singleton& obj) = delete;Singleton& operator=(const Singleton& obj) = delete;public:static Singleton& GetInstance(){return instance;}};Singleton Singleton::instance(10);int main(){Singleton& obja = Singleton::GetInstance();Singleton& objb = Singleton::GetInstance();Singleton& objc = Singleton::GetInstance();Singleton& objd = Singleton::GetInstance();cout << &obja <<endl;cout << &objb << endl;cout << &objc << endl;cout << &objd << endl;return 0;}
运行结果:
饿汉单例模式中,单例对象是通过一个static的静态对象构建的,它在程序启动时,即main函数执行之前,就已经初始化好了,因此不会存在线程安全问题。我们一般多说的线程安全,都是指懒汉模式中的线程安全,稍后会介绍。
懒汉式创建对象
懒汉式创建即:在函数调用该单例对象的时候,先判断这个对象是否已经存在,若存在,直接返回这个对象即可;若还没有创建,就先执行实例化操作。如图:
代码实现如下:
class Singleton{private:int value;static Singleton* pobj;private:Singleton(int x=0):value(x){}Singleton(const Singleton& obj) = delete;Singleton& operator=(const Singleton& obj) = delete;public:static Singleton* GetInstance(int x){if (pobj == NULL){pobj = new Singleton(10);} return pobj; }};Singleton* Singleton::pobj = NULL;int main(){Singleton* obja = Singleton::GetInstance(10);Singleton* objb = Singleton::GetInstance(10);Singleton* objc = Singleton::GetInstance(10);Singleton* objd = Singleton::GetInstance(10);cout << obja <<endl;cout << objb << endl;cout << objc << endl;cout << objd << endl;return 0;}
运行结果:
可以看出,不论定义多少对象指针,它们所指的实例对象只有一个。
懒汉式的线程安全
从上述代码我们可以看出,在单线程中,这已经是一个不错的单例模式,肯定只会有一个单例对象。但是在多线程中,它就有问题了。如图:
如果A线程和B线程同时进入if判断语句,那么此时就会产生两个对象,这不符合单例模式的设计思想,这个时候我们首先想到的是加锁,如下:
static Singleton* GetInstance(int x){std::lock_guardlock(mtx);if (pobj == NULL){std::this_thread::sleep_for(std::chrono::milliseconds(10));pobj = new Singleton(x);}return pobj;}
这种方法解决了线程安全的问题,但是它的效率如何呢?
我们发现,除了第一次实例化对象之外,我们获得的锁有意义外,其它需要直接调用对象的获得的锁好像没有任何意义,我们可否直接将加锁语句放在此if语句里面,如下:
static Singleton* GetInstance(int x){if (pobj == NULL){std::lock_guardlock(mtx);std::this_thread::sleep_for(std::chrono::milliseconds(10));pobj = new Singleton(x);}
仔细观察就会发现问题,如果多个线程同时进入了if语句,那么不仅会有许多线程等待锁,而且会产生很多实例对象,所以我们不能这样做。正确的解决办法如下:
static Singleton* GetInstance(int x){if (pobj == NULL){std::lock_guardlock(mtx);if (pobj == NULL){std::this_thread::sleep_for(std::chrono::milliseconds(10));pobj = new Singleton(x);}}return pobj;}
上述代码很好的解决了线程安全和效率的问题,称为双重校验+加锁:
- 如果pobj不为空,则直接返回,不需要获得锁,不会再降低效率;
- 如果有很多线程都进入第一个if语句,那么其中一个线程会获得锁,之后的线程执行到第二个if语句时,也会直接返回,确保了线程安全;
懒汉式单例模式所造成的内存问题
对于懒汉模式,我们说它是在程序调用的时候才创建的(new出来的),这个时候就涉及到内存释放的问题,在多线程情况下,我们很可能会忘记最后的delete,我们希望它能够自己在函数结束时就自动释。
解决办法:static静态成员变量在程序结束时会自行进行内存的释放,我们可以利用这个特点达成上述目标
代码实现如下:
class Singleton{private:int value;static Singleton* pobj;private:Singleton(int x=0):value(x){} ~Singleton(){cout<<"~Singleton()"<<endl;}Singleton(const Singleton& obj) = delete;Singleton& operator=(const Singleton& obj) = delete;public:static Singleton* GetInstance(int x){if (pobj == NULL){pobj = new Singleton(10);} return pobj; } class Release//定义一个嵌套类 {public: ~Release() {delete pobj;pobj=NULL; } } static Release release;};Singleton* Singleton::pobj = NULL;Singleton::Release Singleton::release;int main(){Singleton* obja = Singleton::GetInstance(10);Singleton* objb = Singleton::GetInstance(10);Singleton* objc = Singleton::GetInstance(10);Singleton* objd = Singleton::GetInstance(10);cout << obja <<endl;cout << objb << endl;cout << objc << endl;cout << objd << endl;return 0;}
运行结果:
懒汉式的一个细节问题
我们如果将懒汉模式的对象初始化放在GetInstance函数中,在多线程情况下会不会出现多次初始化的情况,如下:
class Singleton{private:int value;private:Singleton(int x=0):value(x){} ~Singleton(){cout<<"~Singleton()"<<endl;}Singleton(const Singleton& obj) = delete;Singleton& operator=(const Singleton& obj) = delete;public:static Singleton* GetInstance(int x){ static Singleton* pobj;if (pobj == NULL){pobj = new Singleton(10);} return pobj; } class Release//定义一个嵌套类 {public: ~Release() {delete pobj;pobj=NULL; } } static Release release};
答案是不会的,静态成员变量是在编译时期初始化好的,在主函数执行时,不会在执行初始化语句,所以不用担心线程安全问题。