在多线程环境下,单例模式的实现往往面临“线程安全”与“性能”两难。下面介绍几种常见的实现方式,并给出优缺点分析,帮助你在实际项目中选择最合适的方案。
1. 传统双重检查锁(Double‑Checked Locking)
class Singleton {
public:
static Singleton& instance() {
if (!ptr_) { // 第一次检查
std::lock_guard<std::mutex> lock(mtx_);
if (!ptr_) { // 第二次检查
ptr_.reset(new Singleton);
}
}
return *ptr_;
}
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static std::unique_ptr <Singleton> ptr_;
static std::mutex mtx_;
};
std::unique_ptr <Singleton> Singleton::ptr_;
std::mutex Singleton::mtx_;
- 优点:延迟初始化,只有第一次调用才会产生锁。
- 缺点:在某些编译器和CPU架构下存在指令重排问题,导致线程安全性无法得到完全保证;实现稍显繁琐。
2. 局部静态变量(C++11 后)
class Singleton {
public:
static Singleton& instance() {
static Singleton instance; // C++11 保证线程安全
return instance;
}
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
- 优点:代码简洁,C++11 标准保证了线程安全的初始化;无额外锁开销。
- 缺点:无法自定义构造函数参数;无法在程序退出时控制析构顺序(虽然在大多数实现中已得到妥善处理)。
3. 枚举单例(Enum Singleton)
enum class Singleton {
INSTANCE
};
inline Singleton& getInstance() {
return Singleton::INSTANCE;
}
- 优点:极简实现,天然线程安全,编译时就确定实例。
- 缺点:只能用作无状态标识符,不能包含成员变量或方法;不适用于需要对象特性的场景。
4. 采用 std::call_once 与 std::once_flag
class Singleton {
public:
static Singleton& instance() {
std::call_once(flag_, []() { instance_ = new Singleton(); });
return *instance_;
}
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton* instance_;
static std::once_flag flag_;
};
Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::flag_;
- 优点:显式控制一次性初始化,避免多次检查;与
std::call_once的实现一致,线程安全且性能良好。 - 缺点:需要手动释放实例(可配合
std::shared_ptr或std::unique_ptr自动管理)。
5. 线程安全的懒汉式与饿汉式对比
| 特点 | 懒汉式(延迟) | 饿汉式(预创建) |
|---|---|---|
| 初始化时机 | 第一次访问时创建 | 程序启动时即创建 |
| 线程安全性 | 需要同步机制(如上所示) | 无需同步 |
| 性能 | 可能存在多线程竞争 | 一次性开销较大,后续访问速度快 |
| 资源占用 | 仅在需要时占用 | 永久占用 |
6. 选型建议
- C++11 或更高:优先使用局部静态变量实现,简洁可靠。
- 需要自定义初始化参数:采用
std::call_once+std::unique_ptr。 - 极简需求:枚举单例可满足。
- 对销毁顺序极为敏感:使用
std::unique_ptr或std::shared_ptr手动管理,避免静态析构时机问题。
7. 小结
单例模式的实现不再是“唯一标准”,而是“适配场景”问题。了解各实现方式的机制与适用场景,能够帮助你在项目中快速、可靠地部署单例。祝编码愉快!