在多线程环境下,单例模式的实现需要保证只有一个实例被创建,并且在并发访问时不会出现竞争条件。以下将介绍几种常见的线程安全单例实现方式,并给出完整代码示例。
1. 局部静态变量(C++11 之后的线程安全初始化)
C++11 标准保证了函数内部局部静态变量的初始化是线程安全的。最简单的实现方式就是利用这一特性。
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // 线程安全的局部静态
return instance;
}
// 删除拷贝构造和赋值操作,防止复制
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() {} // 私有构造
};
优点:
- 代码简洁,易于维护。
- 只在第一次调用
getInstance()时创建实例,后续调用不产生额外开销。
缺点:
- 在某些极端情况下,如果初始化时抛出异常,后续调用仍会重新尝试初始化,这可能导致异常重现。
2. 双重检查锁(Double-Checked Locking)
双重检查锁是一种懒汉式的实现,先不加锁检查实例是否存在,只有第一次发现实例为空时才加锁,随后再次检查后才创建实例。
class Singleton {
public:
static Singleton* getInstance() {
if (!instance) { // 第一次检查
std::lock_guard<std::mutex> lock(mutex);
if (!instance) { // 第二次检查
instance = new Singleton();
}
}
return instance;
}
static void destroy() {
std::lock_guard<std::mutex> lock(mutex);
delete instance;
instance = nullptr;
}
private:
Singleton() {}
~Singleton() {}
static std::atomic<Singleton*> instance;
static std::mutex mutex;
};
std::atomic<Singleton*> Singleton::instance{nullptr};
std::mutex Singleton::mutex;
需要注意:
- 这里使用
std::atomic保证指针的可见性。 - 由于
new Singleton()可能发生异常,后续再次获取实例时可能出现重复创建的情况。为此可以使用std::unique_ptr或std::shared_ptr包装。
3. Meyer’s Singleton(局部静态加 std::call_once)
结合 std::call_once 可以更显式地控制初始化逻辑,并确保仅执行一次。
class Singleton {
public:
static Singleton& getInstance() {
std::call_once(initFlag, []() {
instance.reset(new Singleton);
});
return *instance;
}
private:
Singleton() {}
static std::unique_ptr <Singleton> instance;
static std::once_flag initFlag;
};
std::unique_ptr <Singleton> Singleton::instance = nullptr;
std::once_flag Singleton::initFlag;
这种方式的好处是:
- 初始化过程可写成闭包,逻辑更灵活。
- 与
std::call_once的内部实现一样,保证线程安全且只执行一次。
4. 静态成员指针 + 静态局部函数
如果想在单例类之外进行实例化管理,可以使用静态成员指针和静态局部函数来实现。
class Singleton {
public:
static Singleton* instance() {
static Singleton* ptr = init();
return ptr;
}
private:
static Singleton* init() {
return new Singleton();
}
Singleton() {}
};
这里的 static Singleton* ptr 通过 init() 初始化,保证了线程安全。
5. 结合 std::shared_ptr 的线程安全单例
如果单例需要在多个线程中共享,并且希望自动销毁,使用 std::shared_ptr 也是可行的。
class Singleton {
public:
static std::shared_ptr <Singleton> getInstance() {
static std::shared_ptr <Singleton> ptr(new Singleton);
return ptr;
}
private:
Singleton() {}
};
注意:此方法在 C++11 之后同样保证了初始化线程安全。
小结
- 推荐:C++11 之后的局部静态变量实现(Meyer’s Singleton)是最简洁且可靠的方案。
- 特殊需求:若需要自定义销毁时机或延迟初始化,
std::call_once或双重检查锁也可考虑。 - 性能考虑:局部静态变量在第一次调用时才创建,后续访问成本极低,适合大多数场景。
通过上述方法,你可以在多线程环境下安全地实现单例模式,避免了传统实现中的竞态条件和性能问题。