目录
1.简介
单例模式是一个类有且仅有一次在内存中创建对象。当程序多次创建调用同一个类的对象且作用相同时,为了避免多次创建对象消耗资源,单例模式便出现了。单例模式可以让内存中一个类仅有一个实例,并让所有调用者共享这个实例。
1.1 单例模式的类型
1.懒汉式单例:在类加载的时候创建对象,天生是线程安全的。
2.俄汉式单例:在第一次调用的时候创建对象。
1.2 单例模式的特点
1.单例类只能有一个实例
2.单例类必须自己创建自己的唯一实例
3.单例类必须给所有其他对象提供这个一实例
单例模式的写法有好几种,这里主要介绍两种:懒汉式单例、饿汉式单例。
2.饿汉式单例
饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。
#include <cstring>
#include <cstdio>
#include <iostream>
#include <mutex>
#include <vector>
#include <algorithm>
#include <set>
using namespace std;
class Singleton
{
public:
static Singleton *getInstance();
private:
Singleton();
static Singleton *mSingletonInstance;
};
//在类加载的时候,实例化自己
Singleton *Singleton::mSingletonInstance = new Singleton();
Singleton *Singleton::getInstance()
{
return mSingletonInstance;
}
Singleton::Singleton() {}
int main()
{
Singleton *instance1 = Singleton::getInstance();
printf("instance1 = %p\n", instance1);
Singleton *instance2 = Singleton::getInstance();
printf("instance2 = %p\n", instance2);
return 0;
}
由于饿汉式单例会在类加载的时候创建静态对象,如果此对象没有使用,则会浪费一定的内存。因此,产生了懒汉式单例。
3.懒汉式单例
在第一次调用的时候初始化对象。
3.1 有缺陷的懒汉式单例
#include <cstring>
#include <cstdio>
#include <iostream>
#include <mutex>
#include <vector>
#include <algorithm>
#include <set>
using namespace std;
class Singleton
{
public:
static Singleton *getInstance();
private:
Singleton();
static Singleton *mSingletonInstance;
};
Singleton *Singleton::mSingletonInstance = nullptr;
Singleton *Singleton::getInstance()
{
if (mSingletonInstance == nullptr)
{
mSingletonInstance = new Singleton();
}
return mSingletonInstance;
}
Singleton::Singleton() {}
int main()
{
Singleton *instance1 = Singleton::getInstance();
printf("instance1 = %p\n", instance1);
Singleton *instance2 = Singleton::getInstance();
printf("instance2 = %p\n", instance2);
return 0;
}
打印结果:
instance1 = 0x55555556aeb0
instance2 = 0x55555556aeb0
此时在单线程中时安全的,但是在多线程中,是不安全的,因为可能存在两个线程同时调用getInstance方法,同时走进mSingletonInstance == nullptr的判断,此时两个线程会创建两个对象,这与单例的概念不符合。
那么如何解决呢?
加锁。
3.2 线程安全的懒汉式单例
#include <cstring>
#include <cstdio>
#include <iostream>
#include <mutex>
#include <vector>
#include <algorithm>
#include <set>
using namespace std;
class Singleton
{
public:
static Singleton *getInstance();
static std::mutex mSingletonLock;
private:
Singleton();
static Singleton *mSingletonInstance;
};
Singleton *Singleton::mSingletonInstance = nullptr;
std::mutex Singleton::mSingletonLock;
Singleton *Singleton::getInstance()
{
std::lock_guard<std::mutex> lock(mSingletonLock);
if (mSingletonInstance == nullptr)
{
mSingletonInstance = new Singleton();
}
return mSingletonInstance;
}
Singleton::Singleton() {}
int main()
{
Singleton *instance1 = Singleton::getInstance();
printf("instance1 = %p\n", instance1);
Singleton *instance2 = Singleton::getInstance();
printf("instance2 = %p\n", instance2);
return 0;
}
我们加锁后,保证了线程安全,但是每一次获取单例对象时,都要加锁,会影响多线程下的性能。
思路:我们只需要在第一次初始化对象的时候加锁,在后续的获取对象时不用加锁。
解决办法:双重判定。
#include <cstring>
#include <cstdio>
#include <iostream>
#include <mutex>
#include <vector>
#include <algorithm>
#include <set>
using namespace std;
class Singleton
{
public:
static Singleton *getInstance();
static std::mutex mSingletonLock;
private:
Singleton();
static Singleton *mSingletonInstance;
};
Singleton *Singleton::mSingletonInstance = nullptr;
std::mutex Singleton::mSingletonLock;
Singleton *Singleton::getInstance()
{
if (mSingletonInstance == nullptr)
{
std::lock_guard<std::mutex> lock(mSingletonLock);
if (mSingletonInstance == nullptr)
{
mSingletonInstance = new Singleton();
}
}
return mSingletonInstance;
}
Singleton::Singleton() {}
int main()
{
Singleton *instance1 = Singleton::getInstance();
printf("instance1 = %p\n", instance1);
Singleton *instance2 = Singleton::getInstance();
printf("instance2 = %p\n", instance2);
return 0;
}
这样就避免了每次调用都加锁的情况、,同时在第一次调用的时候加锁,保证了只会创建一个对象。
4.懒汉式单例和饿汉式单例的区别
4.1 初始化方面
1.饿汉式会在类加载时,就初始化完成,保证getInstance的时候,单例是已经存在的了。
2.懒汉式只有当第一次i调用getInstance的时候,才会初始化这个单例。
4.2 线程安全方面
1.饿汉式天生就是线程安全的,可以直接用于多线程。
2.懒汉式本身是非线程安全的,需要通过加锁来保证。
4.3 资源消耗方面
1 .饿汉式在类创建的时候就会实例化一个静态对象出来,此对象不管是否使用都会占据内存。
2.懒汉式只有在第一次使用该单例的时候才会实例化对象出来。因此不会浪费内存资源。
4.4 性能方面
1.饿汉式在类创建的时候就会实例化一个静态对象出来,因此在在第一次调用时速度会更快,因为其资源已经初始化完成。
2.懒汉式只有在第一次使用该单例的时候才会实例化对象出来,因此第一次调用要初始化,所以偏慢。