单例模式(恶汉模式、懒汉模式)
单例模式
通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例(即一个类只有一个对象实例),并提供一个访问它的全局访问点,该实例被所有程序模块共享。
如何实现单例模式
《设计模式》一书中给出了一种很不错的实现,定义一个单例类,使用类的私有静态指针变量指向类的唯一实例,并用一个公有的静态方法获取该实例。
分点表述:
- 给单例模式的类提供私有的构造函数
- 在该类中定义一个静态私有对象的指针
- 提供一个静态的公有方法用于创建或获取它本身的静态私有对象
单例模式的两种实现
- 懒汉单例模式:第一次使用时才创建一个唯一的实例对象,从而实现延迟加载的效果。
- 饿汉单例模式:只要程序启动就会创建一个唯一的实例对象,即单例类定义的时候就进行实例化。
懒汉单例模式
-
将构造函数私有化
-
类定义时不创建实例,首次使用的时候再进行创建
-
创建静态成员变量(mInstance)
-
创建公有的访问接口getSingleton()
-
定义内嵌类进行对象的销毁
class CSingleton
{
public:
static CSingleton * getSingleton()//类似与类的构造函数,只不过加了判断条件而已
{
if(mInstance == NULL)
{
mInstance = new CSingleton();
}
return mInstance;
}
class CDestroy
{
public:
~CDestroy()
{
if(CSingleton::mInstance != NULL)
{
delete CSingleton::mInstance;
cout << "~CSingleton()" << endl;
}
}
};
static CDestroy mDel;//当该静态变量销毁时会调用 ~CDestroy(),进而销毁mInstance
private:
static CSingleton * mInstance;
CSingleton()
{
cout << "CSingleton()" << endl;
}
};
CSingleton* CSingleton :: mInstance = NULL;
CSingleton::CDestroy mDel;
int main()
{
//CSingleton s1;
cout << "Process Begin" << endl;
CSingleton *s1 = CSingleton::getSingleton();
CSingleton *s2 = CSingleton::getSingleton();
cout << "s1 = " << s1 << endl << "s2 = "<< s2 << endl;
}
线程安全问题
懒汉单例模式的安全版本
加锁
由于懒汉单例模式的上述情况在多线程的情况下会可能导致不安全状况的发生,因此我们必须加一控制。在这里我们采用加锁机制
静态初始化互斥锁:用一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁
class CSingleton
{
public:
static CSingleton * getSingleton()
{
pthread_mutex_lock(&mutex);//加锁
cout << "lock()" << endl;
if (mInstance == NULL)
{
mInstance= new CSingleton();
}
pthread_mutex_unlock(&mutex);//构造完对象后进行解锁操作
cout << "unlock()" << endl;
return msin;
}
class CDestroy
{
public:
~CDestroy()
{
if(CSingleton::mInstance != NULL)
{
delete CSingleton::mInstance;
cout << "~CSingleton()" << endl;
}
}
};
static CDestroy mDel;
private:
CSingleton()
{
cout << "CSingleton()" << endl;
}
static CSingleton* mInstance;
static pthread_mutex_t mutex;
};
CSingleton* CSingleton::mInstance = NULL;
pthread_mutex_t CSingleton::mutex = PTHREAD_MUTEX_INITIALIZER;//定义一个锁并进行初始化
CSingleton::CDestroy mDel;
int main()
{
cout << "Process Begin" << endl;
CSingleton *s1 = CSingleton::getSingleton();
CSingleton *s2 = CSingleton::getSingleton();
cout << "s1 = " << s1 << endl << "s2 = "<< s2 << endl;
}
加锁的优化
虽然上述的加锁机制已经解决了我们的多线程安全问题,但是每次进行判断之前都要进行加锁以及之后的解锁操作,会大大降低我们的效率。
考虑到只有第一次创建对象的时候需要进行加锁,只要第一次创建对象成功以后,就不必再进行加锁和解锁的操作,只需返回一个指向原来已经创建好的对象的指针即可。
修改函数getSingleton()
static CSingleton* getSingleton()
{
if (mInstance== NULL)
{
pthread_mutex_lock(&mutex);//加锁
cout << "lock()" << endl;
if (mInstance== NULL)
{
mInstance= new CSingleton();
}
pthread_mutex_unlock(&mutex);//解锁
cout << "unlock()" << endl;
}
return mInstance;
}
双重if语句的原因
在多线程的环境中有这样一种情况就是在第一次创建对象的时候两个线程同时到达,即同时调用 getSingleton()方法
而此时由于 mInstance 为 NULL,则两个线程都会进入第一重if语句里边。
由于存在锁机制,所以会有一个线程进入 lock 语句并进入第二个mInstance== NULL ,而另外的一个线程则会在 lock 语句的外面等待。
而当第一个线程执行完 new CSingleton()语句后,便会解锁。
此时,第二个线程便可以进入 lock 语句块,此时,如果没有第二重 mInstance== NULL 的话,那么第二个线程还是可以调用 new CSingleton()语句,这样第二个线程也会创建一个 SingleTon实例,这样也还是违背了单例模式的初衷的。
使用局部静态变量实现懒汉单例模式的安全版本
利用静态局部变量的其中一条性质函数内部的静态局部变量的初始化是在函数第一次调用时执行; 在之后的调用中不会对其初始化。 在多线程环境下,仍能够保证静态局部变量被安全地初始化,并只初始化一次。
#include <iostream>
using namespace std;
class CSingleton
{
public:
static CSingleton *getSingleton()
{
static CSingleton mInstance; //静态局部对象
return &msin;
}
~CSingleton()
{
cout << "~CSingleton()" << endl;
}
private:
CSingleton()
{
cout << "CSingleton()" << endl;
}
};
int main()
{
cout << "Process Begin" << endl;
CSingleton *s1 = CSingleton::getSingleton();
CSingleton *s2 = CSingleton::getSingleton();
cout << "s1 = " << s1 << endl << "s2 = "<< s2 << endl;
}
虽然mInstance 是静态的局部变量,但是编译器在处理上与全局静态变量类似, 均存储在 bss 段
函数中必须要使用static变量的情况:当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类型,则返回为错指针。
那么问题来了??为什么要使用静态变量,和静态成员方法??
- 类的静态成员变量就是指的类共享的变量,而单例模式的对象设成静态就是为了让该类所有实例共享同一个对象,符合要求
- 我们需要一个类的成员方法来创建这个唯一的实例对象,而调用成员方法要么通过对象调用,要么将该成员方法设置为static通过类名直接调用。显然,我们就是通过调用这个成员方法来创建这个唯一的对象,那怎么通过对象调用呢,因此,只能用第二种方法。
饿汉单例模式
-
构造函数私有化
-
在全局进行创建对象
-
让私有的静态类指针指向类的对象
-
公有的静态成员方法作为对外的接口
-
定义一个静态的嵌套类对象的析构函数间接析构单例对象
####如果不把delete封装起来,用户就要自己delete
class CSingleton
{
public:
static CSingleton * getSingleton()
{
return mInstance;
}
private:
static CSingleton *mInstance;
CSingleton()
{
cout << "CSingleton()" << endl;
}
};
CSingleton* CSingleton :: mInstance = new CSingleton();
int main()
{
cout << "Process Begin" << endl;
CSingleton *s1 = CSingleton::getSingleton();
CSingleton *s2 = CSingleton::getSingleton();
cout << "s1 = " << s1 << endl << "s2 = "<< s2 << endl;
delete s1;
}
把delete封装起来遇到的问题
class CSingleton
{
public:
static CSingleton * getSingleton()
{
return mInstance;
}
~CSingleton()
//使用new申请的内存系统是不会主动回收的,需要手动删除,因此我们需要自定义的析构函数delete
{
delete mInstance;
mInstance= NULL;
cout << "~CSingleton()" << endl;
}
private:
static CSingleton * mInstance;
CSingleton()
{
cout << "CSingleton()" << endl;
}
};
CSingleton* CSingleton :: mInstance = new CSingleton();
int main()
{
cout << "Process Begin" << endl;
CSingleton *s1 = CSingleton::getSingleton();
CSingleton *s2 = CSingleton::getSingleton();
cout << "s1 = " << s1 << endl << "s2 = "<< s2 << endl;//会自动调用自定义的析构函数
return 0;
}
问题
####解决办法
class CSingleton
{
public:
static CSingleton * getSingleton()
{
return mInstance;
}
~CSingleton()
{
delete mInstance;
mInstance = NULL;
cout << "~CSingleton()" << endl;
}
class CDestroy
{
public:
~CDestroy()
{
if(CSingleton::mInstance != NULL)
{
delete CSingleton::mInstance;
mInstance = NULL;
cout << "~CSingleton()" << endl;
}
}
};
static CDestroy mDel;
private:
static CSingleton * mInstance;
CSingleton()
{
cout << "CSingleton()" << endl;
}
};
CSingleton* CSingleton :: mInstance = new CSingleton();
CSingleton::CDestroy mDel;
int main()
{
cout << "Process Begin" << endl;
CSingleton *s1 = CSingleton::getSingleton();
CSingleton *s2 = CSingleton::getSingleton();
cout << "s1 = " << s1 << endl << "s2 = "<< s2 << endl;
return 0;
}
我们知道,程序在结束的时候,系统会自动析构所有的全局变量。事实上,系统也会析构所有的类的静态成员变量,就像这些静态成员也是全局变量一样。利用这个特点,我们可以在单例类中定义一个这样的静态成员变量(mDel),而它的唯一工作就是在析构函数中删除单例类的实例。
类CDestroy被定义为CSingleton的公有内嵌类。程序运行结束时,系统会调用CSingleton的静态成员mDel的析构函数,该析构函数会删除单例的唯一实例。