单例模式是一种最为常见的软件设计模式。单例模式要求:单例对象所在的类必须保证只能创建一个对象。单例模式在我们日常生活和软件开发中的应用比比皆是,比如:windows系统只有一个任务管理器,一个市只有一个市长。
如何保证一个类最多只能创建一个对象呢?这个问题不能交由使用者去做处理,比如用全局变量。而应该由这个类的创建者在实现该类的时候考虑问题的解决。
单例模式巧妙的使用C++成员权限,将构造函数和拷贝构造函数隐藏起来(private),从而有效限定使用中对对象的自由创建。然后开放一个(static)接口,通过静态方法创建对象,并在静态方法中限定对象的唯一创建。
单例模式的创建方式一般有两种:懒汉式和饿汉式。
1.懒汉模式
懒汉:顾名思义,不到万不得已该类不会去实例化对象。将对象的示例推迟到需要该对象的时候。
//单例模式之懒汉模式
classSingleton{
public:
staticSingleton*createSingleton(){ //static方法
if(m_handler==nullptr){
m_handler=newSingleton();
}
returnm_handler;
}
private:
Singleton(); //私有化构造函数
~Singleton(); //私有化析构函数
Singleton(constSingleton); //私有化拷贝构造函数,防止通过拷贝构造复制对象
staticSingleton*m_handler;
};
Singleton*Singleton::m_handler=nullptr;
intmain()
{
Singleton*ptr1=Singleton::createSingleton();
Singleton*ptr2=ptr1-createSingleton();//ptr1和ptr2指向同一个对象
return0;
}
2.饿汉模式
饿汉:单例类在创建类的时候就创建了对象。
//单例模式之饿汉模式
classSingleton{
public:
staticSingleton*getSingleton(){
returnm_handler;
}
private:
Singleton(); //私有化构造函数
~Singleton(); //私有化析构函数
Singleton(constSingleton); //私有化拷贝构造函数,防止通过拷贝构造复制对象
staticSingleton*m_handler;
};
Singleton*Singleton::m_handler=newSingleton; //类创建时,创建对象
intmain()
{
Singleton*ptr1=Singleton::createSingleton();
Singleton*ptr2=ptr1-createSingleton();//ptr1和ptr2指向同一个对象
return0;
}
3.单例模式中的线程安全
前面我们考虑了单例模式的懒汉式和饿汉式,但是我们只考虑了普通单线程情况。如果考虑到多线程情况,那么上面的懒汉模式则不是线程安全的。而饿汉模式因为在编译阶段已经创建了对象,所有它是线程安全的。
如何解决懒汉模式的线程不安全呢?通常情况我们可以通过互斥锁解决临界资源的访问问题。
//单例模式之懒汉模式+线程安全
classSingleton{
public:
staticSingleton*createSingleton(){ //static方法
if(m_handler==nullptr){//解决访问效率问题
pthread_mutex_lock(m_lock);
if(m_handler==nullptr){
m_handler=newSingleton();
}
pthread_mutex_unlock(m_lock);
returnm_handler;
}
}
private:
Singleton(); //私有化构造函数
~Singleton(); //私有化析构函数
Singleton(constSingleton); //私有化拷贝构造函数,防止通过拷贝构造复制对象
staticSingleton*m_handler;
staticpthread_mutex_tm_lock;
};
Singleton*Singleton::m_handler=nullptr;
pthread_mutex_tSingleton::m_lock=PTHREAD_MUTEX_INITIALIZER;
intmain()
{
Singleton*ptr1=Singleton::createSingleton();
Singleton*ptr2=ptr1-createSingleton();//ptr1和ptr2指向同一个对象
return0;
}
上面例程通过互斥锁,看似解决了多线程中的临界资源互斥问题。但是实际上并非如此。具体问题如下:
上面代码中:m_handler=newSingleton();我们期望的执行顺序是:
(1)分配一段内存(2)构造对象,放入内存(3)m_handler存内存地址
但是实际执行可能是:
(1)分配一段内存(2)m_handler存内存地址(3)构造对象,放入内存
那么后面的情况可能导致,对象还没创建,但是已经被另外一个线程拿去使用了,这种情况可能导致严重错误。那么如何解决呢?大家可以思考一下。
文章来源:学到牛牛