C++设计模式--单例模式详解(懒汉模式、饿汉模式、双重锁)
应用场景
通常我们在做通讯的时候,我们跟服务器数据交互,假如每次需要通讯交互我们都创建一个实例来通讯的话,那么其实这个通讯的交互可能是不安全的和不简洁的,因为多个通讯实例对象的交互在不同线程操作的时候会存在不同步的情况,同时多个实例的操作会增加交互流程的复杂度。或者再说,我们给系统做一个日志模块的时候,我们希望做到的当然是只有一个操作对象,可不想多个实例对象对同一个日志文件频繁的文件操作,这种的执行效果处理不佳,BUG会频繁找你。一、单例模式是什么?
单例模式(Singleton)是保证一个类仅有一个实例,并且要提供一个访问这个类的全局访问点(提供访问接口函数)。
它同全局变量不同的是,全局变量可以被多个类使用,但是它不能防止被实例化多个对象,而单例模式的类对象可以保证没有被其他实例可以被创建,并且它可以提供一个访问该实例的方法。
二、使用步骤
1.UML图
2.代码实现
Singleton类,定义一个GetInstance操作,允许客户访问它的唯一实例。GetInstance是一个静态方法,主要是负责创建自己的唯一实例。
方法1:
#include <iostream>
using namespace std;
class Singleton
{
private:
Singleton()
{
m_Test = 520;
}
static Singleton *m_Instance; // 这里必须是静态对象
int m_Test;
public:
static Singleton *GetInstance()
{
// 第一次调用GetInstance发现m_Instance是空指针的时候会先创建,这个唯一对象被创建
// 接着后面再调用的话只是直接返回已创建的对象而已
if (m_Instance == NULL )
m_Instance = new Singleton ();
return m_Instance;
}
// 释放对象
static void ReleaseInstance()
{
if (m_Instance != NULL )
{
delete m_Instance;
m_Instance = NULL ;
}
}
// 提供类对象操作接口
int GetTest()
{
return m_Test;
}
};
// 静态类对象的初始化方式
Singleton *Singleton ::m_Instance = NULL;
int main(int argc , char *argv [])
{
Singleton *pObj1= Singleton ::GetInstance();
Singleton *pObj2= Singleton ::GetInstance();
// 比较两次实例化后的对象的结果是实例相同
if(pObj1 == pObj2)
printf("two obj is same....");
cout<<pObj1->GetTest()<<endl;
cout<<pObj2->GetTest()<<endl;
Singleton ::ReleaseInstance();
return 0;
}
方法1应该是我们平时最简单常见的方式,但是这个方式有个问题就是,如果是多线程使用的话,多个线程同时,注意是同时访问Singleton类,调用GetInstance()方法,会有可能造成创建多个实例的。
方法2:
#include <iostream>
using namespace std;
class Singleton
{
private:
Singleton()
{
m_Test = 520;
}
static Singleton *m_Instance; // 这里必须是静态对象
int m_Test;
public:
static Singleton *GetInstance()
{
// 加锁处理多线程问题
Lock(); // C++没有直接的Lock操作,请使用其它库的Lock,比如Boost,此处仅为了演示说明
if (m_Instance == NULL )
m_Instance = new Singleton ();
UnLock();
return m_Instance;
}
};
// 静态类对象的初始化方式
Singleton *Singleton ::m_Instance = NULL;
这里lock是确保当一个线程位于代码的临界区时,另一个线程不会进入临界区,如果其他线程试图进入锁定的代码,则它将一直等待,直到
该对象被释放。
此处进行了两次m_Instance == NULL的判断,是借鉴了Java的单例模式实现时,使用的所谓的“双重锁定”机制。因为进行一次加锁和解锁是需要付出对应的代价的,而进行两次判断,就可以避免多次加锁与解锁操作,同时也保证了线程安全。如果进行大数据的操作,加锁操作将成为一个性能的瓶颈。
方法3:
#include <iostream>
using namespace std;
class Singleton
{
private:
Singleton()
{
m_Test = 520;
}
static Singleton *m_Instance; // 这里必须是静态对象
int m_Test;
public:
static Singleton *GetInstance()
{
return const_cast <Singleton *>(m_Instance);
}
};
// 静态类对象的初始化方式
const Singleton *Singleton ::m_Instance = new Singleton();
因为静态初始化在程序开始时,也就是进入主函数之前,由主线程以单线程方式完成了初始化,所以静态初始化实例保证了线程安全性。在性能要求比较高时,就可以使用这种方式,从而避免频繁的加锁和解锁造成的资源浪费。这种静态初始化的方式是在自己被加载时就将自己实例化,所以就被称之为饿汉式单例类,而原先方法1-2的处理方式是要在第一次被引用的时候,才会被自己实例化,所以我们也称之它为懒汉式单例类。
上述三种实现,都要考虑到实例的销毁,以下两种可以针对实例销毁的方式。,
方法4:
#include <iostream>
using namespace std;
class Singleton
{
private:
Singleton()
{
m_Test = 520;
}
static Singleton *m_Instance; // 这里必须是静态对象
int m_Test;
public:
static Singleton *GetInstance()
{
static Singleton m_Instance;
return &m_Instance;
}
};
因为静态类对象不是通过指针创建,所以自然没有对应的delete。但是大多时候,我们可能会在new完一个对象之后,就会可能会对它进行释放,那么有没有一种方式能让它自动销毁,类似智能指针的方式呢。看下一个实现方式。
方法5:
#include <iostream>
using namespace std;
class Singleton
{
public:
static Singleton *GetInstance()
{
return m_Instance;
}
int GetTest()
{
return m_Test;
}
private:
Singleton()
{
m_Test = 520;
}
static Singleton *m_Instance;
int m_Test;
// 创建释放类,用来自动销毁对象
class GC
{
public :
~GC()
{
// 释放类对象
if (m_Instance != NULL )
{
cout<<"delete instance..." <<endl;
delete m_Instance;
m_Instance = NULL ;
}
}
};
static GC gc;
};
// 初始化静态变量
Singleton *Singleton ::m_Instance = new Singleton();
Singleton ::GC Singleton ::gc;