6 种 单例 的手写,都是懒汉(饿汉代码在 “懒汉 / 饿汉的区别”)
目录
✊前言
TinyWebServer 涉及到单例模式,花个 5 小时学习一下
参考文章👇 + GPT
🌼GPT解析
概念
优势
劣势
应用
🌼概念解析
RAII
懒汉 / 饿汉的区别
- 懒汉式 -- 时间换空间 -- 访问量较小(推荐内部静态变量的懒汉单例,代码量少)
- 饿汉式 -- 空间换时间 -- 访问量较大 OR 线程较多的的情况
懒汉模式
// Singleton类的定义
class Singleton {
public:
// 获取单例对象的静态成员函数
static Singleton* GetInstance() {
// 如果instance为空,即还没有创建单例对象
if (instance == nullptr) {
// 创建一个新的Singleton对象
instance = new Singleton();
}
// 返回单例对象的指针
return instance;
}
private:
// 构造函数和析构函数,设置为默认实现
Singleton() = default;
~Singleton() = default;
// 禁用拷贝构造函数和赋值运算符重载,确保单例对象唯一性和不会被复制
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 静态成员变量,指向单例对象。初始值为nullptr,表示还未创建单例对象
static Singleton *instance;
};
// 初始化静态成员变量
Singleton* Singleton::instance = nullptr;
懒汉 -- 解释
饿汉模式
class Singleton {
public:
static Singleton* GetInstance() {
return instance;
}
private:
Singleton() = default; // 默认构造函数,设为 private,防止外部实例化对象
~Singleton() = default; // 默认析构函数,设为 private,防止外部删除对象
Singleton(const Singleton&) = delete; // 删除拷贝构造函数,防止对象被复制
Singleton& operator=(const Singleton&) = delete; // 删除赋值运算符重载,防止对象被赋值
static Singleton *instance; // 静态成员变量,指向单例对象
};
Singleton* Singleton::instance = new Singleton(); // 在程序启动时即创建单例对象
特点
举例
单例 -- 伪代码
/*
数据库类会对`getInstance() 获取实例`方法
进行定义,以便客户端在程序各处都能访问
相同的数据库连接实例
*/
class Database is
// 保存单例实例的成员变量 -- 声明为静态
private static field instance : Database
// 单例的构造函数 -- 私有,防止`new`运算符
// 直接调用构造方法
private constructor Database() is
// 初始化代码(eg:数据库服务器的实际连接)
// 用于控制对单例实例的访问权限的静态方法
public static method getInstance() is
if (Database.instance == null)
acquireThreadLock()
// 确保在该线程等待解锁时
// 其他线程没有初始化该实例
if (Database.instance == null)
Database.instance = new Database()
return Database.instance
// 最后,任何单例都必须定义一些可在其实例上执行的业务逻辑
public method query(sql) is
// eg: 所有数据库查询请求都需通过该方法进行
// 因此,可以在这里添加限流或缓冲逻辑
class Application is
method main() is
Database foo = Database.getInstance()
foo.query("SELECT ...")
Database bar = Database.getInstance()
bar.query("SELECT ...")
// 变量 `bar` 和 `foo` 中包含同一个对象
适用场景
🧜1)某个类,对于所有客户端,只有一个可用实例
🤴2)需要严格控制全局变量
单例 -- 实现方式
优缺点
单例拥有与全局变量相同的优缺点。 尽管它们非常有用, 但却会破坏代码的模块化特性
优点
缺点
🎂手写 6 种单例模式
大厂面试真题
🐬(一)懒汉 -- 内存泄露
// 版本一:简单懒汉模式,存在内存泄漏和线程安全问题
class Singleton1 {
public:
// 获取单例实例的静态方法
// 用于获取 Singleton1 类的唯一实例
static Singleton1* GetInstance() {
if (instance == nullptr)
instance = new Singleton1(); // 创建新实例
return instance; // 返回 Singleton1 实例
}
private:
// 防止外界 构造/删除 对象
Singleton1() = default; // 私有构造
~Singleton1() = default; // 私有析构函数
Singleton1(const Singleton1&) = default; // 禁止拷贝构造
// 禁止赋值
Singleton1& operator = (const Singleton1&) = default;
// 静态成员指针,指向唯一实例
static Singleton1 *instance;
};
Singleton1* Singleton1::instance = nullptr;
/*
存在内存泄漏问题,instance 在堆上,需要手动释放
但是外界无法调用delete释放对象(私有静态成员指针)
&&
线程安全问题 -- 多个线程同时分配内存
*/
🐬(二)懒汉 -- 解决内存泄漏
补充知识👇
代码
// 版本二:解决内存泄漏,但仍存在线程安全问题
class Singleton2 {
public:
// 获取单例实例的静态方法
static Singleton2* GetInstance() {
if (instance == nullptr) {
instance = new Singleton2();
atexit(Destructor); // 程序退出时释放
}
return instance;
}
private:
// 防止外界 构造/删除 对象
Singleton2() = default; // 私有构造
~Singleton2() = default; // 私有析构
Singleton2(const Singleton2&) = default; // 禁止拷贝构造
// 禁止赋值 -- 通过定义为私有,避免外界调用,达到了禁止的目的
Singleton2& operator=(const Singleton2&) = default;
// 释放实例资源的静态方法
static void Destructor() {
if (instance != nullptr) {
delete instance;
instance = nullptr;
}
}
// 静态成员指针,指向唯一实例
static Singleton2 *instance;
};
Singleton2* Singleton2::instance = nullptr;
/*
解决了内存泄漏,程序退出时释放对象
但仍存在多个线程同时分配内存的问题
*/
🐆(三)懒汉 -- 双检锁
需要注意的是,双检锁 -- Java下正确,Cpp下错误。具体参考👇
Ubuntu Pastebin
补充解释
关于 双检锁 两个 if 的解释
双检锁 -- 最终效果:只有第一个线程加锁,并创建单例实例
代码
/*
版本三
双重检查锁(double-checked-locking)实现线程安全
*/
class Singleton3 {
public:
// 获取单例实例的静态方法
// 静态成员函数 返回 指向 Singleton3 类型对象的指针
static Singleton3* GetInstance() {
// 双检锁 - 两个 if,只有指针为空才加锁,避免每次调用
// GetInstance() 都加锁,降低加锁的开销
if (instance == nullptr) {
lock_guard<mutex> lock(mutex_); // 第一个线程加锁
if (instance == nullptr) {
instance = new Singleton3(); // 第一个线程创建实例
atexit(Destructor);
}
}
return instance;
}
private:
// 防止外界 构造/删除 对象
Singleton3() = default; // 私有构造
~Singleton3() = default; // 私有析构
Singleton3(const Singleton3&) = default; // 禁止拷贝
Singleton3& operator=(const Singleton3&) = default; // 禁止赋值
// 释放实例资源的静态方法
static void Destructor() {
if (instance != nullptr) {
delete instance;
instance = nullptr;
}
}
static Singleton3 *instance; // 静态成员指针,指向唯一实例
static mutex mutex_; // 互斥锁,用于线程安全
}
// 定义静态指针成员 instance,用于保存 Singleton3 类的唯一实例
Singleton3* Singleton3::instance = nullptr;
// nullptr 表示未创建实例
🐆(四)原子操作
补充解释
代码
// 版本四:原子操作 + 内存屏障 保证线程安全
class Singleton4 {
public:
// 获取实例的静态方法
// 静态成员函数 返回 指向 Singleton4 类型对象的指针
static Singleton4* GetInstance() {
// 加载单例实例指针
Singleton4* tmp = instance.load(std::memory_order_relaxed);
// 获取内存屏障 - 确保前面操作完成
atomic_thread_fence(std::memory_order_acquire);
if (tmp == nullptr) {
// 加锁保证线程安全
std::lock_guard<std::mutex> lock(mutex_); // 加锁
// 重新加载单例实例指针
tmp = instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton4; // 创建实例
// 释放内存屏障 - 确保存储操作不被重排序
atomic_thread_fence(std::memory_order_release);
// 存储新创建的实例指针
instance.store(tmp, std::memory_order_relaxed);
atexit(Destructor); // 注册析构函数
}
}
return tmp;
}
private:
// 防止外界 构造/删除 对象
Singleton4() = default; // 私有构造
~Singleton4() = default; // 私有析构
Singleton4(const Singleton4&) = default; // 拷贝构造
Singleton4& operator=(const Singleton4&) = default; // 赋值
// 释放实例资源的静态方法
static void Destructor() {
if (instance != nullptr) {
delete instance; // 删除实例
instance = nullptr; // 重置指针
}
}
static std::atomic<Singleton4*> instance; // 原子指针 - 线程安全
static std::mutex mutex_; // 互斥锁 - 用于双检锁
};
std::atomic<Singleton4*> Singleton4::instance;
std::mutex Singleton4::mutex_;
🐘(五)C++11 -- magic static
补充解释
关于 new 操作带来的 cpu reorder 操作
简而言之 -- new 和 多线程 可能会产生冲突(需要采取其他措施来规避 BUG)
代码
有了 C++11 magic static 后,就不用在 懒汉模式 下加锁了,因为静态成员只初始化 1 次
// 版本五:C++11 magic static 特性实现线程安全
class Singleton5 {
public:
// 获取单例实例
static Singleton5& GetInstance() {
/*
(1)静态局部变量初始化时,并发线程同时进入声明语句,
并发线程会阻塞等待其初始化结束,保证线程安全
(2)静态局部变量首次经过它的声明才会被初始化,在其后所有调用中,
声明都会被跳过
(3)So, 定义在栈上的局部静态变量保存单例对象的
优势:延迟加载;系统自动调用析构函数;回收内存;
没有 new 操作 带来的 cpu reorder 操作;线程安全
*/
static Singleton5 instance; // magic static 特性
return instance;
}
private:
// 防止外界 构造/删除 对象
Singleton5() = default; // 私有构造
~Singleton5() = default; // 私有析构
Singleton5(const Singleton5&) = default; // 拷贝构造
Singleton5& operator=(const Singleton5&) = default; // 赋值
};
🐘(六)模板类
补充解释
模板复习
代码
// 版本六:模板实现单例模式
template<typename T>
class Singleton6 {
public:
// 获取单例实例
static T& GetInstance() {
static T instance;
return instance;
}
protected: // 为了基类可以 构造/析构(仅限类内部和基类使用)
// 析构虚函数, 支持多态,确保子类析构
virtual ~Singleton6() = default;
Singleton6() = default; // 私有构造
Singleton6(const Singleton6&) = default; // 禁止拷贝构造
Singleton6& operator=(const Singlton6&) = default; // 禁止赋值
};
// Singleton6<DesignPattern> 指定模板类型为 DesingnPattern
class DesignPattern : public Singleton6<DesignPattern> {
// 友元:基类访问父类私有不受限制
friend class Singleton6<DesignPattern>;
public:
~DesignPattern() = default; // 析构在public, 便于手动删除对象
private:
DesignPattern() = default; // 私有构造
DesignPattern(const DesignPattern&) = default; // 禁止拷贝
// 禁止赋值
DesignPattern& operator=(const Designpattern&) = default;
};
int main()
{
// 获取单例实例
DesignPattern &d1 = DesignPattern::Getinstance();
}