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