什么是单例模式?
** 单例模式其实就是设计一个类,确保只能实例化一个对象,并向系统提供这个函数**
单例模式有什么好处?
- 在内存中只有一个对象,节省内存空间;
- 避免频繁的创建销毁对象,可以提高系统性能;
- 避免对共享资源的多重利用;
- 可以全局访问
实现方法:
一、 因为只能有一个实例、所以将 构造函数、赋值运算符函数、拷贝构造函数写在私有下,禁止他人访问;如果不写在私有下,别人就能调用这三个函数来创建实例,无法实现单例模式。
二、那这样的话,我们设计的这个类就无意义了(因为在私有下啊,那创建的实例,系统获取不到啊,它又必须得给系统提供这个实例),那么为了使得类变得有意义我们可以在类中提供一个接口(static的getinstance公有函数)来获取到这个实例(静态私有对象)。****
接口特征:
- 不能依赖对象调用
- 不能返回类类型(会有临时对象的生成)
#include<iostream>
#include<vector>
#include<string>
#inlcude<algorithm>
using namespace std;
class SingleTon
{
private:
int i;
SingleTon (int x):i(x); //构造函数: 不可访问
void operaotr =(SingleTon &); //赋值操作函数: 不允许赋值运算
SingleTon (const SingleTon &); //拷贝构造函数: 不允许拷贝构造
public:
static SingleTon & instance() //返回一个事例的引用; 类的对象
{
static SingleTon s(100);
return s;
}
int getValue() //取值
{
return i;
}
void setValue(int x) //重新赋值
{
i=x;
}
};
int main(void)
{
SingleTon & s = SingleTon::instance(); //构造函数是私有类型,不可访问,故使用的是:引用
cout<<s.getValue()<<endl; //100
SingkeTon & s2 = SingleTon::instance();
cout<<s2.getValue()<<endl; //100
s2.setValue(20);
cout<<s2.getValue()<<endl; //20
cout<<s.getValue()<<endl; //20
system("pause");
return 0;
}
单例模式一般根据实例化对象的时机不同分为这两类:
- 懒汉模式——延时加载
- 第一次用到类实例的时候才会去实例化
- 懒汉模式是线程不安全的,在并发的时候,会出现多个 SingleTon实例
- 初始写法:
class SingleTon
{
private:
singleTon(){}; //私有构造函数
static SingleTon *p; //单例对象
public:
static SingleTon *GetInstance()
{
if(p == nullpter)
{
p = new SingleTon();
}
return p;
}
}
这种实现方式是线程不安全的,为什么说不安全,请看:
- 要实现线程安全,必须对getInstance 进进行改造,以确保安全
class SingleTon
{
private:
SingleTon(){} //私有构造方法
static Singleton *p; //类定义声明
public :
//DCL 双重检测机制
static Singleton* GetInstance()
{
if(p==nullptr) //双重检测机制
{
lock(); //加同步锁
if(p==nullptr) //双重检测机制
{
p= new Singleton();
}
unlock();
}
reutrn p;
}
//在getIntance中做了两次NULL检查,加了两次锁,确保了只有在第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗问题。
//进去临界区以后,还要做一次判空。因为当两个线程同时访问的时候,线程A构建完对象,线程B也已经通过了最初的判空验证,不做第二次判空的话,线程B仍然会再次构建instance对象。
如图看解析:为什么双重检测机制就保证线程安全了:
- 饿汉模式——贪婪加载
饿汉、饥不择食,什么都吃,什么都要。所以不管用不用,他都要创建对象(即在类创建的时候就创建好实例)