文章目录
目录
一、单例模式是什么?
1.定义:
单例模式是一种设计模式,它保证了一个类只有一个对象,具体分为懒汉模式以及饿汉模式。
单例模式有三个要点:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
2.为什么要用单例模式:
某些类只需要一个对象,多个对象的创建浪费资源,比如某些管理器。
对于某些类,我们只需要一个对象实例,当一个实例创建成功,所有操作都基于这个实例来完成。这时就会用到单例模式。
二、如何实现单例
为了防止外部创建对象,我们将构造函数私有化,并在内部提供静态私有指针变量,设置一个公共的静态方法返回对象实例。
1.基本创建
class animal
{
private:
animal() {}
static animal* a ;
public:
static animal* get()
{
if (a == nullptr)
{
a = new animal;
}
return a;
}
};
animal* animal::a = nullptr;
设置成静态可在类外访问,无需创建对象,获取对象直接通过get获取唯一对象实例(分配空间)。静态变量,在类外初始化。
三.懒汉和饿汉模式
1.懒汉模式:
懒汉模式会在需要的时候创建并返回类的实例,和上面基本创建类似,但是懒汉模式会存在线程安全问题,需要加锁。
class animal
{
private:
animal() {}
static animal* a ;
static mutex* mut;
public:
static animal* get()
{
mut->lock();
if (a == nullptr)
{
a = new animal;
}
mut->unlock();
return a ;
}
};
animal* animal::a = nullptr;
mutex* animal::mut = new mutex();
1.1线程安全问题
class animal
{
private:
animal() {}
static animal* a ;
static mutex mt;
public:
static animal* get()
{
if (a == nullptr)
{
std::this_thread::sleep_for(std::chrono::milliseconds(10));
a = new animal;
}
return a;
}
};
animal* animal::a = nullptr;
void fun()
{
animal* q = animal::get();
cout << q;
}
int main()
{
for (int i = 0; i < 2; i++)
{
thread* th = new thread(fun);
}
while (1);
}
这里说一下volatile,这个关键字会声明一个可能在程序控制之外被修改的变量,也就说可能是多线程访问的变量,有些编译器可能会将多次被访问的变量存入寄存器,避免了多次的多次的读入,但是存的时候可能已被修改,但是寄存器中还是未修改,这时使用volatile声明保证了变量多线程的可见性,也就是禁止存入寄存器,都从内存中操作,但这只保证了可见性,实际访问还得用锁,保证原子性。
当我们在多线程下使用单例模式创建实例,会出现创建出多个实例的情况:
可以看到,得到的两个地址是不同的。
原因:当第一个线程想要获取并创建实例之前,另一个线程也在获取,这时实例没有创建完成,导致两个线程都认为自己是先到的,然后创建了两个实例。
解决办法:加锁,同一时刻只能有一个线程去创建。
1.2 c++11懒汉模式
c++11 特性,如果多个线程尝试初始化静态局部变量,那么只会初始化一次。
2.饿汉模式
饿汉模式,编译时创建唯一实例(分配空间),没有线程安全问题,但是浪费资源。
class animal
{
private:
animal() {}
static animal* a ;
public:
static animal* get()
{
return a ;
}
};
animal* animal::a = new animal();
3.两种模式的对比:
从速度上看:饿汉模式提前创建好实例,相较于懒汉模式,速度更快。
从空间利用看:懒汉模式在使用时才实例化,空间利用率更高。
四.个人理解
1.为什么不直接 static animal a; 然后返回&a?
这个a属于静态变量声明,需要类外初始化,如果是指针*a 可以初始化为nullptr,a没法初始化,会有错误。而c++11懒汉模式,中的a是局部静态对象,如果发现多线程初始化则会阻塞相应线程,保证单例。
是否会造成线程安全问题,在于是否会进行多次实例化,而静态局部变量只会初始化一次,不用实例化。
总结
单例模式优点:
1.系统内存中只存在一个对象,因此可以节约系统资源。
2.因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它。
单例模式缺点:
1.由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
2. 单例类的职责过重,在一定程度上违背了“单一职责原则”。
3.现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例对象状态的丢失。
适用场景:
(1) 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。
(2) 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。