1.单例模式介绍
单例模式是一种对象创建型模式,使用单例模式,可以保证为一个类只生成唯一的实例对象。也就是说,在整个程序空间中,该类只存在一个实例对象。
即:
保证一个类、只有一个实例存在,同时提供能对该实例加以访问的全局访问方法
结构图:
为什么要使用单例模式?
在应用系统开发中,我们常常有以下需求:
- 在多个线程之间,比如初始化一次socket资源;共享同一个资源或者操作同一个对象
- 在整个程序空间使用全局变量,共享资源
- 大规模系统中,为了性能的考虑,需要节省对象的创建时间等等。
因为Singleton模式可以保证为一个类只生成唯一的实例对象,所以这些情况,Singleton模式就派上用场了。
如何构建单例模式
1)构造函数私有化
2)增加静态私有的当前类的指针变量
3)提供静态对外接口,可以让用户获得单例对象,
即提供一个全局的静态方法(全局访问点)
单例模式分为懒汉式和饿汉式
2.懒汉模式
懒汉式:调用的时候才会new一个对象
缺点:这个实现在单线程下是正确的,但在多线程情况下,
如果两个线程同时首次调用GetInstance方法且同时检测到Instance是NULL,
则两个线程会同时构造一个实例给Instance,这样就会发生错误
示例代码说明:
namespace SingleTon_lazy_Test
{
class SingleTon_lazy
{
public:
//提供静态对外接口,可以让用户获得单例对象
static SingleTon_lazy* getInstance() {
//判断会否为第一次调用
if (instance == NULL) {
instance = new SingleTon_lazy;
}
return instance;
}
private:
//构造函数私有化
SingleTon_lazy() {
std::cout << "new SingleTon_lazy.." << std::endl;
}
private:
//增加静态私有的当前类的指针变量
static SingleTon_lazy* instance;
//禁止拷贝和赋值
SingleTon_lazy(const SingleTon_lazy&) {}; //禁止拷贝
SingleTon_lazy& operator=(const SingleTon_lazy&) {}; //禁止赋值
};
//类外初始化
SingleTon_lazy* SingleTon_lazy::instance = NULL;
}
int main()
{
//懒汉模式测试
SingleTon_lazy_Test::SingleTon_lazy::getInstance();
}
3.饿汉模式
饿汉式: 程序开始运行时,直接new一个对象
特点:
饿汉式的特点是一开始就加载了,如果说懒汉式是“时间换空间”,
那么饿汉式就是“空间换时间”,因为一开始就创建了实例,
所以每次用到的之后直接返回就好了。饿汉模式是线程安全的.
示例代码说明:
namespace SingleTon_hungry_Test
{
class SingleTon_hungry
{
public:
static SingleTon_hungry* getInsatnce() {
return pInstance;
}
private:
SingleTon_hungry() {
std::cout << " new SingleTon_hungry..." << std::endl;
}
private:
static SingleTon_hungry* pInstance;
SingleTon_hungry(const SingleTon_hungry&) {}; //禁止拷贝
SingleTon_hungry& operator=(const SingleTon_hungry&) {}; //禁止赋值
};
SingleTon_hungry* SingleTon_hungry::pInstance = new SingleTon_hungry();
}
int main()
{
//饿模式测试
SingleTon_hungry_Test::SingleTon_hungry::getInsatnce();
}
4.多线程+单例模式
多线程下的单例模式
懒汉式碰到多线程,是线程不安全,饿汉式是线程安全的
在上述懒汉模式的代码下,假如多个线程都调用了getinstance方法,且都走到了判断为空那一步,可能同时成立。这样会导致多个线程同时创建instance对象,即创建了多次,出现线程不安全的情况。因此,懒汉模式线程是不安全的。
示例代码说明:
加锁的经典懒汉实现
(线程安全)
//加锁的经典懒汉实现(线程安全)
namespace SingleTon_lazy_MultiThread_Test
{
std::mutex single_mut;
class SingleTon_lazy_MultiThread
{
public:
//提供静态对外接口,可以让用户获得单例对象
//使用double-check来保证线程安全,但是如果处理大量数据时,该锁才成为严重的性能瓶颈
static SingleTon_lazy_MultiThread* getInstance() {
//判断会否为第一次调用
if (instance == NULL) {
///只有当instance等于null时,才开始使用锁机制 二次检查
std::unique_lock<std::mutex> munique(single_mut);
if (instance == NULL) {
instance = new SingleTon_lazy_MultiThread;
}
}
return instance;
}
static void test()
{
single_mut.lock();
single_mut.unlock();
}
private:
//构造函数私有化
SingleTon_lazy_MultiThread() {
std::cout << "new SingleTon_lazy_MultiThread." << std::endl;
}
private:
//增加静态私有的当前类的指针变量
static SingleTon_lazy_MultiThread* instance;
SingleTon_lazy_MultiThread(const SingleTon_lazy_MultiThread&) {};//禁止拷贝
SingleTon_lazy_MultiThread& operator=(const SingleTon_lazy_MultiThread&) {}; //禁止赋值
};
//类外初始化(静态成员变量不依赖于任何对象,需要在类外单独分配空间)
SingleTon_lazy_MultiThread* SingleTon_lazy_MultiThread::instance = NULL;
}
int main() {
//单例不需要考虑释放问题,因为单例只申请了一个对象的内存空间,
//程序结束时会自动释放掉,不会产生内存泄漏问题;
SingleTon_lazy_MultiThread_Test::SingleTon_lazy_MultiThread::getInstance();
}
5.单例模式的适用场景
单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等
- 需要频繁实例化然后销毁的对象。
- 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
- 有状态的工具类对象。
- 频繁访问数据库或文件的对象
6.静态成员函数及变量扩展说明
为什么需要要构建静态变量及函数?
需要结合静态成员函数及变量的特性,
类的成员函数有如下特性:
-静态成员函数是类的一个特殊的成员函数
-静态成员函数属于整个类所有,没有this指针
-静态成员函数只能直接访问静态成员变量和静态成员函数
-可以通过类名直接访问类的公有静态成员函数
-可以通过对象名访问类的公有静态成员函数
-定义静态成员函数,直接使用static关键字修饰即可
静态成员变量特点:
-静态成员变量属于整个类所有
-静态成员变量的生命期不依赖于任何对象,为程序的生命周期
-可以通过类名直接访问公有静态成员变量
-所有对象共享类的静态成员变量
-可以通过对象名访问公有静态成员变量
-静态成员变量需要在类外单独分配空间
-静态成员变量在程序内部位于全局数据区 (Type className::VarName = value)