单例模式(Singleton)
单例模式是一种创建型设计模式, 保证一个类只有一个实例,并提供一个访问它的全局访问点
单例模式解决了两个问题:
- 保证一个类只有一个实例。控制类的实例数量的常见原因是控制某些共享资源(例如数据库或文件)的访问权限。
- 为该实例提供一个全局访问节点 。
但是单例模式违反了单一职责原则
单例模式的实现方式
- 在类中添加一个私有静态成员变量用于保存单例实例
- 声明一个共有静态构造方法用于获取单例实例
- 在静态方法中实现"延迟初始化"。 该方法会在首次被调用时创建一个新对象, 并将其存储在静态成员变量中。 此后该方法每次被调用时都返回该实例。
- 将类的构造函数设为私有。类的静态方法仍能调用构造函数,但是其他对象不能调用
- 检查客户端代码,将对单例的构造函数的调用替换为对其静态构建方法的调用。
c++
#include <iostream>
#include <string>
using namespace std;
class Singleton
{
public:
// 本类实例的唯一全局访问点
static Singleton &GetInstance()
{
// 如果实例不存在,创建实例
if (instance == nullptr)
{
instance = new Singleton();
cout << "首次创建实例" << endl;
}
else
{
cout << "重复创建,返回同一个实例" << endl;
}
return *instance;
}
~Singleton(){
delete instance;
instance = nullptr;
}
private:
// 私有的静态指针,指向单例
static Singleton *instance;
// 私有的构造函数,防止外部代码创建类的实例
Singleton() {}
};
// 静态成员需要在类外定义
Singleton *Singleton::instance = nullptr;
// 客户端代码
static void Client()
{
Singleton &s1 = Singleton::GetInstance();
Singleton &s2 = Singleton::GetInstance();
Singleton &s3 = Singleton::GetInstance();
}
int main()
{
Client();
return 0;
}
输出为
首次创建实例
重复创建,返回同一个实例
重复创建,返回同一个实例
多线程时候的单例模式
如果main函数改写成
c++
int main()
{
// 线程容器
vector<thread> threads;
// 创建100个线程
for (int i = 0; i < 10; i++){
threads.emplace_back(Client);
}
// 等待所有线程完成
for(auto& th:threads){
th.join();
}
return 0;
}
注意将s2和s3注释掉了
输出变为
重复创建,返回同一个实例首次创建实例重复创建,返回同一个实例
首次创建实例
首次创建实例
重复创建,返回同一个实例
重复创建,返回同一个实例
重复创建,返回同一个实例
重复创建,返回同一个实例重复创建,返回同一个实例
出现了线程安全问题,有多个线程首次创建了实例
原因是GetInstance方法中条件判断(if (instance == nullptr))和实例创建(instance = new Singleton();)之间的代码片段并不是原子操作,可能会有多个线程同时进入这个条件判断,并且创建多个实例。
加锁的版本
c++
class Singleton
{
public:
// 本类实例的唯一全局访问点
static Singleton &GetInstance()
{
// 加锁
lock_guard<mutex> lock(mutex_);
// 如果实例不存在,创建实例
if (instance == nullptr)
{
instance = new Singleton();
cout << "首次创建实例" << endl;
}
else
{
cout << "重复创建,返回同一个实例" << endl;
}
return *instance;
}
~Singleton()
{
delete instance;
instance = nullptr;
}
private:
// 私有的静态指针,指向单例
static Singleton *instance;
// 私有的构造函数,防止外部代码创建类的实例
Singleton() {}
// 锁
static mutex mutex_;
};
// 静态成员需要在类外定义
Singleton *Singleton::instance = nullptr;
mutex Singleton::mutex_;
加锁解决了线程安全的问题,但是新的问题来了,不管三七二十一都加锁,这样很影响性能
双重检查锁
c++
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <mutex>
using namespace std;
class Singleton
{
public:
// 本类实例的唯一全局访问点
static Singleton &GetInstance()
{
// 如果实例不存在,创建实例
if (instance == nullptr)
{
// 加锁
lock_guard<mutex> lock(mutex_);
// 第二次检查
if (instance == nullptr)
{
instance = new Singleton();
cout << "首次创建实例" << endl;
}
}
else
{
cout << "重复创建,返回同一个实例" << endl;
}
return *instance;
}
~Singleton()
{
delete instance;
instance = nullptr;
}
private:
// 私有的静态指针,指向单例
static Singleton *instance;
// 私有的构造函数,防止外部代码创建类的实例
Singleton() {}
// 锁
static mutex mutex_;
};
// 静态成员需要在类外定义
Singleton *Singleton::instance = nullptr;
mutex Singleton::mutex_;
// 客户端代码
static void Client()
{
Singleton &s1 = Singleton::GetInstance();
// Singleton &s2 = Singleton::GetInstance();
// Singleton &s3 = Singleton::GetInstance();
}
int main()
{
// 线程容器
vector<thread> threads;
// 创建100个线程
for (int i = 0; i < 10; i++){
threads.emplace_back(Client);
}
// 等待所有线程完成
for(auto& th:threads){
th.join();
}
return 0;
}
饿汉式
上面的代码是单例模式的懒汉式。所谓懒汉式,就是在程序需要用到这个类的实例的时候采取创建,所以在多线程的场景下存在线程安全问题。
饿汉式,是指在程序在首次加载类的时候就会实例化,所以不存在线程安全问题。
代码如下
c++
// 饿汉式实现
#include <iostream>
#include <string>
#include <thread>
#include <vector>
using namespace std;
class Singleton
{
public:
// 本类实例的唯一全局访问点
static Singleton &GetInstance()
{
// 直接返回已经初始化的实例
return instance;
}
// 禁止拷贝构造函数和赋值操作符
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
// 私有的静态实例,在类声明时初始化
static Singleton instance;
// 私有的构造函数,防止外部代码创建类的实例
Singleton()
{
cout << "首次创建实例" << endl;
}
};
// 静态成员需要在类外定义
Singleton Singleton::instance;
// 客户端代码
static void Client()
{
Singleton &s = Singleton::GetInstance();
}
int main()
{
// 线程容器
vector<thread> threads;
// 创建10个线程
for (int i = 0; i < 10; i++)
{
threads.emplace_back(Client);
}
// 等待所有线程完成
for(auto& th:threads)
{
th.join();
}
return 0;
}