1. 什么是单例模式
单例模式是一种创建型 设计模式,确保一个类只有一个实例 ,并提供全局访问点。它常用于需要控制资源共享的场景,如数据库连接、日志系统或配置管理。
单例模式的核心特点
- 唯一实例:类只能创建一个对象实例。
- 全局访问:通过静态方法或属性提供全局访问入口。
- 私有构造:防止外部直接通过构造函数实例化。
2. 单例模式常见的实现方式有哪些?
2.1 懒汉式(线程不安全)
在首次调用时创建实例,适合非多线程环境。
cpp
class Singleton {
private:
static Singleton* instance;
Singleton() {} // 私有构造函数
public:
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
};
Singleton* Singleton::instance = nullptr;
缺点:多线程环境下可能创建多个实例。
2.2 懒汉式(线程安全,加锁)
cpp
#include <mutex>
class Singleton {
private:
static Singleton* instance;
static std::mutex mtx;
Singleton() {}
public:
static Singleton* getInstance() {
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
};
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;
通过互斥锁保证线程安全,但锁开销影响性能。
2.3 饿汉式(线程安全)
cpp
class Singleton {
private:
static Singleton instance;
Singleton() {}
public:
static Singleton* getInstance() {
return &instance;
}
};
Singleton Singleton::instance;
程序启动时即初始化实例,无需考虑线程安全问题,但可能浪费资源(实际可以忽略啦,比较推荐)。
2.4 Meyers' Singleton(静态局部变量)
arduino
class Singleton {
private:
Singleton() {}
public:
static Singleton& getInstance() {
static Singleton instance;
return instance;
}
};
C++11后静态局部变量初始化是线程安全的,既延迟加载又无需显式加锁。
2.5 双重检查锁定(DCLP)
cpp
class Singleton {
private:
static Singleton* instance;
static std::mutex mtx;
Singleton() {}
public:
static Singleton* getInstance() {
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) {
instance = new Singleton();
}
}
return instance;
}
};
减少锁的竞争,但需注意内存屏障问题(C++11后可用atomic解决)。
2.如何保证线程安全
在C++中实现单例模式时,通常会将构造函数、拷贝构造函数和赋值运算符设为私有,并通过静态方法获取唯一实例。基础的非线程安全实现如下(懒汉式):
cpp
class Singleton {
private:
static Singleton* instance;
Singleton() {} // 私有构造函数
Singleton(const Singleton&) = delete; // 禁用拷贝
Singleton& operator=(const Singleton&) = delete; // 禁用赋值
public:
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
};
Singleton* Singleton::instance = nullptr;
在多线程环境下,多个线程可能同时进入if (instance == nullptr)
判断,导致创建多个实例,违背单例模式原则。
懒汉式线程不安全的原因
当线程A和线程B同时调用getInstance()
时:
- 两者都可能检测到
instance == nullptr
- 两者都会执行
new Singleton()
- 最终导致内存泄漏和实例不唯一
线程安全的改进方案
方案1:加锁(双重检查锁定)
cpp
class Singleton {
private:
static Singleton* instance;
static std::mutex mtx;
Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static Singleton* getInstance() {
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) {
instance = new Singleton();
}
}
return instance;
}
};
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;
方案2:局部静态变量(C++11起线程安全)
cpp
class Singleton {
private:
Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static Singleton& getInstance() {
static Singleton instance;
return instance;
}
};
在C++11之前,局部静态变量的初始化在多线程环境下可能存在竞态条件,导致多次初始化或未定义行为。C++11标准明确规定了局部静态变量的线程安全初始化机制。
线程安全初始化的实现原理:
C++11要求编译器在局部静态变量初始化时插入线程同步代码。通常采用**双重检查锁定模式(Double-Checked Locking Pattern)**实现:
- 第一次检查避免每次调用都加锁
- 加锁后第二次检查确保只有一个线程执行初始化
- 使用内存屏障保证可见性
假如我们这么使用局部变量
cpp
void foo() {
static MyClass instance;
// 使用instance
}
编译器生成的等效线程安全代码可能类似:
cpp
void foo() {
static MyClass* instance = nullptr;
static std::atomic_flag flag;
if (!instance) {
std::lock_guard guard(lock);
if (!instance) {
instance = new MyClass();
}
}
// 使用*instance
}
局部静态变量线程安全初始化适用于:
- 单例模式的实现(Meyers' Singleton)
- 需要延迟初始化的全局资源
- 线程安全的惰性初始化
需要注意:
- 析构顺序仍然是未定义的
- 不同编译单元的静态变量初始化顺序问题仍然存在
- 性能敏感场景应考虑其他方案
替代方案比较
对于需要更高性能的场景,可以考虑:
- 使用函数外的静态变量(牺牲延迟初始化特性)
- 显式使用std::call_once
- 依赖项注入模式
cpp
// 使用std::call_once的示例
void foo() {
static std::once_flag flag;
static MyClass* instance;
std::call_once(flag, [](){
instance = new MyClass();
});
// 使用*instance
}
方案3:原子操作(C++11)
cpp
#include <atomic>
class Singleton {
private:
static std::atomic<Singleton*> instance;
Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static Singleton* getInstance() {
Singleton* tmp = instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
tmp = instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton();
std::atomic_thread_fence(std::memory_order_release);
instance.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}
};
std::atomic<Singleton*> Singleton::instance;
std::mutex Singleton::mtx;
最佳实践建议
对于现代C++项目,推荐使用方案2的局部静态变量实现:
- 代码简洁
- C++11标准保证线程安全
- 不需要手动管理内存
- 按需初始化(真正懒汉式)
早期C++版本或需要更精细控制时,可采用方案1的双重检查锁定模式。方案3适用于需要极致性能的场景,但代码复杂度较高。