单例模式
定义
单例模式(Singleton),保证一个类仅有一个实例,并提供了访问它的全局访问点。
“通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象,一个最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。”
实现方式
class Singleton {
private:
static Singleton* instance; // 静态实例指针
Singleton() {} // 私有构造函数
public:
// 禁止拷贝构造和赋值操作符
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
};
// 静态成员变量初始化
Singleton* Singleton::instance = nullptr;
线程安全
当多个线程几乎同时进入 getInstance() 方法,并且 instance 仍然是空指针的情况下。各线程可能会同时发现 instance 是空的,然后各自创建一个新的实例,这就违反了单例模式的唯一实例原则。
例子说明
假设 Singleton::getInstance() 实现如下:
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton(); // 多个线程可能同时执行到这一步
}
return instance;
}
在这种情况下,线程A和线程B可能几乎同时进入 getInstance() 方法,且都发现 instance == nullptr,于是它们各自创建了一个 Singleton 实例。这就违背了单例模式的初衷。
解决方案
要在多线程环境中确保单例模式的正确性,必须对实例创建过程进行同步。常用的解决方案包括:
- 使用互斥锁(Mutex)
在创建实例的代码块上使用互斥锁,以确保同一时刻只有一个线程能进入该代码块。虽然有效,但锁可能导致性能下降,特别是在高并发的情况下。
static Singleton* getInstance() {
std::lock_guard<std::mutex> lock(mutex); // 锁住代码块
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
private:
static std::mutex mutex; // 静态互斥锁
- 双重检查锁定(Double-Checked Locking)
为了减少不必要的锁操作,可以使用双重检查锁定。在进入锁定区之前先检查一次 instance,只有在 instance 为空时才加锁进行第二次检查。
static Singleton* getInstance() {
if (instance == nullptr) { // 第一次检查
std::lock_guard<std::mutex> lock(mutex);
if (instance == nullptr) { // 第二次检查
instance = new Singleton();
}
}
return instance;
}
- 静态局部变量 (C++11 起支持)
C++11 引入的静态局部变量的线程安全性可以避免手动加锁。在 C++11 标准中,静态局部变量的初始化是线程安全的,这样可以简化代码:
static Singleton& getInstance() {
static Singleton instance; // 静态局部变量,C++11 保证线程安全
return instance;
}
应用场景
配置管理:应用程序的全局配置类。
日志管理:记录日志的类,确保所有模块使用同一个日志实例。
资源管理:数据库连接池、线程池等资源管理类。
单例模式的优点:
- 控制实例数量,节省资源。
- 提供全局访问点,易于管理共享资源。
缺点:
可能导致过多的全局状态,影响模块化和测试性。
在多线程环境下,需要额外的同步机制。