单例模式:懒汉式 vs 饿汉式
·
单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供全局访问点。实现方式主要分为懒汉式(Lazy Initialization)和饿汉式(Eager Initialization),两者的核心区别在于实例化时机和线程安全性。
一、懒汉式(Lazy Initialization)
特点:
- 延迟实例化:直到第一次调用
getInstance()时才创建实例。 - 资源优化:适用于实例创建代价高或可能不被使用的场景。
- 线程安全需额外处理:原生实现非线程安全,需通过锁机制或 C++11 特性保证安全。
1. 基础懒汉式(非线程安全)
class LazySingleton {
private:
static LazySingleton* instance;
LazySingleton() = default; // 私有构造函数
~LazySingleton() = default;
LazySingleton(const LazySingleton&) = delete; // 禁用拷贝构造
LazySingleton& operator=(const LazySingleton&) = delete; // 禁用赋值运算符
public:
static LazySingleton* getInstance() {
if (instance == nullptr) { // 第一次检查:若实例未创建
instance = new LazySingleton(); // 创建实例
}
return instance;
}
};
LazySingleton* LazySingleton::instance = nullptr; // 静态成员初始化
问题:
- 线程不安全:多线程环境下,若多个线程同时进入
if (instance == nullptr),可能创建多个实例。
2. 线程安全的懒汉式(双重检查锁定,DCL)
#include <mutex>
class LazySingleton {
private:
static LazySingleton* instance;
static std::mutex mutex_;
LazySingleton() = default;
~LazySingleton() = default;
LazySingleton(const LazySingleton&) = delete;
LazySingleton& operator=(const LazySingleton&) = delete;
public:
static LazySingleton* getInstance() {
if (instance == nullptr) { // 第一次检查(无锁):提高性能
std::lock_guard<std::mutex> lock(mutex_); // 加锁
if (instance == nullptr) { // 第二次检查(有锁):确保线程安全
instance = new LazySingleton();
}
}
return instance;
}
};
LazySingleton* LazySingleton::instance = nullptr;
std::mutex LazySingleton::mutex_;
原理:
- 第一次检查:无锁快速判断实例是否已创建,减少锁竞争开销。
- 第二次检查:加锁后再次验证,确保只有一个线程创建实例。
- 内存屏障:C++11 后编译器会自动优化,避免指令重排序导致的问题(如
instance先赋值但对象未完全构造)。
3. C++11 Meyers 单例(推荐实现)
class LazySingleton {
private:
LazySingleton() = default;
~LazySingleton() = default;
LazySingleton(const LazySingleton&) = delete;
LazySingleton& operator=(const LazySingleton&) = delete;
public:
static LazySingleton& getInstance() {
static LazySingleton instance; // C++11保证线程安全的局部静态变量
return instance;
}
};
优势:
- 自动线程安全:C++11 标准规定静态局部变量的初始化是线程安全的,无需手动加锁。
- 自动释放:静态变量在程序结束时自动析构,无需担心内存泄漏。
- 代码简洁:避免了双重检查的复杂性。
二、饿汉式(Eager Initialization)
特点:
- 立即实例化:在程序启动(静态变量初始化阶段)时就创建实例。
- 线程安全:由静态变量初始化机制保证,无需额外同步。
- 资源占用:无论是否使用,实例始终存在,可能浪费资源。
1. 基础饿汉式
class EagerSingleton {
private:
static EagerSingleton instance; // 静态实例
EagerSingleton() = default;
~EagerSingleton() = default;
EagerSingleton(const EagerSingleton&) = delete;
EagerSingleton& operator=(const EagerSingleton&) = delete;
public:
static EagerSingleton& getInstance() {
return instance;
}
};
EagerSingleton EagerSingleton::instance; // 程序启动时初始化
关键点:
- 静态成员变量
instance在程序进入main()前就完成初始化,由编译器保证线程安全。 - 适用于实例创建开销小且必定会被使用的场景。
2. 静态指针版饿汉式
class EagerSingleton {
private:
static EagerSingleton* instance;
EagerSingleton() = default;
~EagerSingleton() = default;
EagerSingleton(const EagerSingleton&) = delete;
EagerSingleton& operator=(const EagerSingleton&) = delete;
public:
static EagerSingleton* getInstance() {
return instance;
}
};
EagerSingleton* EagerSingleton::instance = new EagerSingleton(); // 程序启动时初始化
区别:
- 使用指针而非引用,需手动管理内存(但静态指针在程序结束时自动释放)。
- 延迟了解引用操作(
*instance),可能略微提高性能。
三、对比与选择
| 维度 | 懒汉式(Meyers 单例) | 饿汉式 |
|---|---|---|
| 实例化时机 | 第一次调用getInstance()时 | 程序启动时(静态初始化阶段) |
| 线程安全性 | 无需额外同步(C++11 保证) | 天然线程安全(静态变量特性) |
| 资源占用 | 按需创建,避免浪费 | 始终占用内存,即使未使用 |
| 创建失败处理 | 可在运行时捕获异常(如内存不足) | 初始化失败可能导致程序无法启动 |
| 复杂度 | 中等(需理解静态局部变量特性) | 低(简单直接) |
| 适用场景 | 实例创建开销大、可能不被使用的场景 | 实例创建开销小、程序启动时必须存在 |
四、进阶问题
1. 单例实例的销毁
- 饿汉式:静态实例由系统自动释放,无需处理。
- 懒汉式(指针版):需确保在程序结束前释放资源,可通过
atexit()注册销毁函数:static void destroyInstance() { delete instance; instance = nullptr; } // 在getInstance()中注册 std::atexit(destroyInstance);
2. 多进程 / 多 DLL 场景
- 单例模式仅保证单进程内唯一,多进程环境需通过共享内存或文件锁实现跨进程单例。
- 在 DLL 中使用单例时,需注意不同模块可能加载不同的 DLL 实例,导致多个单例存在。
3. 替代方案
- 依赖注入(DI):通过参数传递实例,避免全局状态,提高可测试性。
- 静态工具类:若无需维护状态,可使用静态方法替代单例(如
MathUtils)。
五、面试高频问题
-
为什么懒汉式需要双重检查锁定?
- 答:第一次检查提高性能(无锁快速判断),第二次检查确保线程安全(加锁后验证)。
-
饿汉式是否绝对线程安全?
- 答:在单进程环境下是线程安全的,但若涉及动态链接库(DLL)加载,可能在多模块中创建多个实例。
-
Meyers 单例的线程安全是如何实现的?
- 答:C++11 标准规定,静态局部变量的初始化在多线程环境下是线程安全的,编译器会自动生成同步代码。
-
单例模式如何防止反射攻击?
- 答:在 Java 中可通过枚举实现单例(天然防止反射),C++ 中需在构造函数中检查实例是否已存在,若存在则抛出异常。
-
单例模式的优缺点?
- 优点:控制资源、全局访问、避免重复创建。
- 缺点:违反单一职责、单测困难、可能导致内存泄漏。
更多推荐
所有评论(0)