> 文档中心 > 单例模式介绍

单例模式介绍

目录

引入

单例模式概念

单例模式的设计类型

饿汉式创建对象

懒汉式创建对象

懒汉式的线程安全

懒汉式单例模式所造成的内存问题


引入

设想一下,你现在正在使用编译器写代码,此时你点工具箱,它会显示出来一个功能界面,你再点一下,他就会关闭,不会再显示出另一个界面。如图:

它永远不会是这样:

 

不论你点多少次,它只会显示一次。这其实就是一个单例模式。

单例模式概念

单例模式是指一个类有且仅有一个实例,并提供一个可以访问它的全局访问点,即:在内存中仅会创建一次对象。

就像上图所示的,我们每次点击工具栏后,弹出的页面都是一样的,那么我们就没必要让它弹出很多次。这里的每一个页面我们可以设想为一个对象,如果我们创建很多个对象,但是它们的作用都是相同的,那这是对内存资源的极大浪费(每次创建对象都会开辟内存)。最好的解决办法就是:我们只创建一个对象,并提供一个可以访问它的方法,让所有需要的函数都共享这一对象。

举例:

单例模式的设计类型

单例模式的类型有两种:

  • 懒汉式:在真正需要使用时采取创建该对象。
  • 饿汉式:在类加载时已经创建好该对象,等着被调用。

当你肚子不太饿时,你去饭店可以等待一下,让厨师给你现做,可以理解为懒汉式;

当你非常饿时,你提前预约,告诉饭店老板,你现在给我做,我马上过去,等你到达时,菜已经提前做好了,可以理解为饿汉式;

 饿汉式创建对象

饿汉式创建对象即:在类加载时,单例对象已经被提前创建好了,当程序调用时,直接返回这个对象即可。

代码实现:

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};

答案是不会的,静态成员变量是在编译时期初始化好的,在主函数执行时,不会在执行初始化语句,所以不用担心线程安全问题。

冰雪之城