一、饿汉模式与懒汉模式
http://m.blog.csdn.net/snow_5288/article/details/76306226
先了解一下什么是设计模式?
设计模式的定义: 设计模式是一套被反复使用,多数人知晓的、经过分类编目的、代码设计的总结,使用设计模式是为了可重用代码,让代码更容易被他人理解,保证代码可靠性。
设计模式的分类:按照目的可分为三类,创建型模式,结构型模式,行为型模式;按照范围,即模式主要处理类之间的关 系还是对象之间的关系分为类模式和对象模式,设计模式主要被广泛应用于面向对象编程。
从最简单的单例模式开始学起:单例模式,根据名字拆分就可以知道这个模式的意思,即单个实例的模式。
什么是单个实例的模式,既然是面向对象,那么一个类只允许产生一个实例对象的话,就是单例模式。且单例类提供获取这个唯一实例的接口
饿汉单例:即在最开始的时候,静态对象就已经创建完成;
设计方法是类中包含一个静态成员指针,该指针指向该类的一个对象,提供一个公有的静态成员方法,返回该对象指针;为了使得对象唯一,还需要将构造函数设为私有,
单例模式之懒汉单例:
(解决:线程安全问题(上锁),死锁问题(利用RAII,双检查),利用RAII(资源获得初始化,及自己定义Lock类,并完成构造与析构),保证用的同一把锁(利用引用),双检查)
所谓懒汉模式,就是尽可能晚的创建这个对象的实例,即在单例类第一次被引用时将自己初始化;其实C++里很多地方都是类似这样的思想,比如晚绑定,写时拷贝技术等,就是尽量使资源的利用最大化,不要让空闲的人还占着有限的资源。
懒汉单例的实现:其实就是将静态对象指针初始化为NULL(这就是与饿汉模式的最大不同),当单例类被引用的时候,先进行判断指针是否为NULL,为NULL的时候再去实例一个对象,不会NULL时,直接返回静态对象指针;达到保证只有一个实例的目的。
单例的分类:懒汉模式(lazy load)、饿汉模式。
懒汉模式:只有当调用对象是才创建对象–相对而言,复杂—-但适用性高,各种场景下都适用。
饿汉模式:在函数一开始就创建(main)–简单高效,但是在某些场景下有缺陷—-适用性会受到限制,动态库。
单例模式的实现过程中,需要注意以下三点:
1. 单例类的构造函数、拷贝构造、赋值运算符重载都为私有;
2. 提供一个私有的静态私有成员指针变量;(在类外进行初始化)
3. 提供一个公有的静态工厂方法(用来返回这唯一一个对象);
单例模式的线程安全问题:
以懒汉单例为例,如果此时多线程进行操作,简单点以两个线程为例,假设pthread_1刚判断完 intance 为NULL 为真,准备创建实例的时候,切换到了pthread_2, 此时pthread_2也判断intance为NULL为真,创建了一个实例,再切回pthread_1的时候继续创建一个实例返回,那么此时就不再满足单例模式的要求了, 既然这样,是因为多线程访问出的问题,那我们就来加把锁,使得线程同步;
单例模式的优点
1. 提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它;
2. 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。
3. 允许可变数目的实例,基于单例模式我们可以扩展,使用与单例控制相似的方法来获得指定个数的对象实例。(即单例类内有多个静态对象指针成员,每次当单例类被引用时随机分配一个实例对象);
单例模式的缺点
1. 因为单例模式没有抽象层,所以单例类的扩展有很大的困难;
2. 单例类的职责过重,即是工厂角色,提供了工厂的方法,同时又充当了产品的角色;
3. 滥用单例类会带来一些负面问题;
单例模式的适用场景
1. 系统只需要一个实例对象,或者考虑到资源消耗的太大而只允许创建一个对象。
2. 客户调用类的单个实例只允许使用一个公共访问点,除了该访问点之外不允许通过其它方式访问该实例 (就是共有的静态方法)
二、代码块
#include<iostream>
using namespace std;
#include<assert.h>
//单例模式
//一、饿汉模式一
class Hungry1
{
public:
static Hungry1& GetInstance()
{
assert(_inst);
return *_inst;
}
void Print()
{
cout << "Hungry1->" << _a << endl;
}
private:
Hungry1() :_a(0)//构造函数私有化
{
cout << "Hungry1" << endl;
}
Hungry1(const Hungry1& h);//防拷贝 在类内只给声明,不给定义
Hungry1& operator = (const Hungry1& h);
static Hungry1* _inst;//静态成员在类外进行初始化
// 指向实例的指针定义为静态私有,这样定义静态成员函数获取对象实例
int _a;
};
Hungry1* Hungry1::_inst = new Hungry1();//静态成员在main函数之前初始化
void Hungry1Test()
{
cout << "main.c" << endl;//打这一句是为了证明,Hungry的对象是在main之前就已经初始化了
//Hungry h1 = Hungry::GetInstance();//拷贝不了
Hungry1::GetInstance().Print();
Hungry1::GetInstance().Print();
Hungry1::GetInstance().Print();
}
//饿汉模式二
class Hungry2
{
public:
static Hungry2& GetInstance()
{
static Hungry2 _inst;//静态变量只会创建一次
return _inst;
}
void Print()
{
cout << "Hungry2->" << _a << endl;
}
private:
Hungry2() :_a(0)//构造函数私有化
{
cout << "Hungry2" << endl;
}
Hungry2(const Hungry2& h);//防拷贝 在类内只给声明,不给定义
Hungry2& operator = (const Hungry2& h);
int _a;
};
void Hungry2Test()
{
cout << "main.c" << endl;//打这一句是为了证明,Hungry的对象是在main之前就已经初始化了
//Hungry h1 = Hungry::GetInstance();//拷贝不了
Hungry2::GetInstance().Print();
Hungry2::GetInstance().Print();
Hungry2::GetInstance().Print();
}
//单例模式二
//懒汉模式一
class Singleton1
{
public:
static Singleton1& GetInstance()
{
if (_inst == NULL)
{
_inst = new Singleton1();
cout << "once" << endl;
}
else
{
cout << "not once" << endl;
}
return *_inst;
}
void Print()
{
cout << "Singleton1->" << _a << endl;
}
private:
Singleton1() :_a(0)
{
cout << "Singleton1" << endl;
}
Singleton1(const Singleton1& s1);
Singleton1& operator = (const Singleton1& s1);
static Singleton1* _inst;
int _a;
};
Singleton1* Singleton1::_inst = NULL;
void Singleton1Test()
{
cout << "main.c" << endl;
//Singleton1 s1;//创建不了对象
Singleton1::GetInstance().Print();
Singleton1::GetInstance().Print();
Singleton1::GetInstance().Print();
}
//懒汉模式二
#include<mutex>
class Lock
{
public:
Lock(mutex& mtx)
:_mtx(mtx)
{
_mtx.lock();//加锁
}
~Lock()
{
_mtx.unlock();//解锁
}
private:
Lock(const Lock& mtx);//防止拷贝
Lock& operator = (const Lock& mtx);//防止赋值
mutex& _mtx;//这里加上引用很重要(保证每次使用的是同一把锁)
};
class Singleton2
{
public:
static Singleton2& GetInstance()
{
if (_inst == NULL)//双检查机制,只有创建实例的时候才进行加锁解锁来提高代码效率
{
//_mtx.lock();//加锁,加锁期间其余线程不能访问该临界区
Lock lock(_mtx);//RAII机制(自己进行加锁与解锁操作)
//利用RAII机制自己“造轮子”避免死锁
//RAII:资源获取即初始化,相当于资源分配和初始化时原子操作,其中不会中断
//lock_guard<mutex>lock(_mtx);//调用库里的函数来避免自己造轮子的开销
if (_inst == NULL)//只有当第一次进来_inst为空的时候才创建实例
{
_inst = new Singleton2();
cout << "once" << endl;
//内存栅栏(防止编译器优化)保证构造函数先执行
//保证tmp已完成构造函数的初始化
//Singleton2* tmp = new Singleton2;
//MemoryBarrier();//优化----内存栅栏,防止编译器对指令流水进行乱序优化
//_inst = tmp;
}
//_mtx.unlock();//解锁
}
else
{
cout << "not once" << endl;
}
return *_inst;
}
void Print()
{
cout << "Singleton2->" << _a << endl;
}
private:
Singleton2() :_a(0)
{
cout << "Singleton2" << endl;
}
Singleton2(const Singleton2& s1);
Singleton2& operator = (const Singleton2& s1);
static Singleton2* _inst;// 指向实例的指针定义为静态私有,这样定义静态成员函数获取对象实例
int _a; // 单例类里面的数据
static mutex _mtx; // 保证线程安全的互斥锁
};
Singleton2* Singleton2::_inst = NULL;//给静态指针赋初值为NULL (这点很重要)
mutex Singleton2::_mtx ;//_mtx会调用mutex默认的无参构造函数,所以不用初始化
void Singleton2Test()
{
cout << "main.c" << endl;
//Singleton1 s1;//创建不了对象
Singleton2::GetInstance().Print();
Singleton2::GetInstance().Print();
Singleton2::GetInstance().Print();
}