饿汉模式的单例本身就是线程安全的
class Singleton {
public:static Singleton* getInstance() {return m_instance;}static void deleteInstance() {if (m_instance) {delete m_instance;m_instance = nullptr;}}private:Singleton() { std::cout << "constructor" << std::endl; }~Singleton() { std::cout<< "destructor" << std::endl; }Singleton(const Singleton &signal) = delete;Singleton& operator=(const Singleton &signal) = delete;private:static Singleton *m_instance;
};// 代码一运行就初始化创建实例 ,本身就线程安全
Singleton* Singleton::m_instance = new (std::nothrow) Singleton();int main () {Singleton* ps = Singleton::getInstance();Singleton::deleteInstance();return 0;
}
class Singleton {
private:Singleton() { std::cout << "constructor" << std::endl; }~Singleton() { std::cout<< "destructor" << std::endl; }Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& singleton) = delete;static Singleton* m_instance; //全局的唯一实例public:static Singleton* getInstance() {if (m_instance == nullptr) {m_instance = new Singleton();}return m_instance;}static void deleteInstance() {if (m_instance) delete m_instance;m_instance = nullptr;}};int main () {Singleton* ps = Singleton::getInstance();Singleton::deleteInstance();return 0;
}
Singleton* Singleton::m_instance = nullptr;
这种实现对于单线程程序是可用的,但是一旦面临多线程,就会出现线程安全的问题。下面给出线程安全的版本
class Singleton {
private:Singleton() { std::cout << "constructor" << std::endl; }~Singleton() { std::cout<< "destructor" << std::endl; }Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& singleton) = delete;static std::mutex m_mutex;static Singleton* m_instance; //全局的唯一实例public:static Singleton* getInstance() {std::unique_lock lock(m_mutex);if (m_instance == nullptr) {m_instance = new Singleton();}return m_instance;}static void deleteInstance() {std::unique_lock lock(m_mutex);if (m_instance != nullptr) delete m_instance;m_instance = nullptr;}
};
Singleton* Singleton::m_instance = nullptr;
std::mutex Singleton::m_mutex;int main () {Singleton* ps = Singleton::getInstance();Singleton::deleteInstance();return 0;
}
这样做是线程安全的,但是每次调用 getInstance() 都需要加锁解锁,相当于将读取 m_instance 的操作进行了排队,极大地损失了效率。下面给出更高效的实现
class Singleton {
private:Singleton() { std::cout << "constructor" << std::endl; }~Singleton() { std::cout<< "destructor" << std::endl; }Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& singleton) = delete;static std::mutex m_mutex;static Singleton* m_instance; //全局的唯一实例public:static Singleton* getInstance() {if (m_instance == nullptr) { //只有当m_instance确实为空才用加锁std::unique_lock lock(m_mutex);if (m_instance == nullptr) {m_instance = new Singleton();}}return m_instance;}static void deleteInstance() {std::unique_lock lock(m_mutex);if (m_instance != nullptr) delete m_instance;m_instance = nullptr;}
};
Singleton* Singleton::m_instance = nullptr;
std::mutex Singleton::m_mutex;int main () {Singleton* ps = Singleton::getInstance();Singleton::deleteInstance();return 0;
}
为什么要double check?
如果我们将上面的实现的第二个检查去掉,会导致什么问题:
static Singleton* getInstance() {if (m_instance == nullptr) { //只有当m_instance确实为空才用加锁std::unique_lock lock(m_mutex);m_instance = new Singleton();}return m_instance;
}
如果有两个线程都通过了if判断,那么他们都会经历 加锁-new对象-解锁 的过程,所以new会被执行两次,违背了单例模式的规则。所以我们需要在锁前和锁后检查两次
注意双检查锁看似是线程安全的,但实际并非如此
正常的new顺序:分配内存-执行构造函数-返回内存地址
但实际在指令层面,CPU可能打乱顺序:分配内存-返回内存地址-执行构造函数
这样,当一个线程执行完“分配内存-返回内存地址”,还没来得及调用构造函数时,切换到另一个线程执行,它发现 m_instance 不是null,于是返回一个没有构造完成的“对象”,导致了错误
这是编译器对指令进行reorder优化导致的问题,各个语言几乎都存在这个问题
C++的解决方案是加入 memory_fence
class Singleton {
private:Singleton() { std::cout << "constructor" << std::endl; }~Singleton() { std::cout<< "destructor" << std::endl; }Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& singleton) = delete;static std::mutex m_mutex;static std::atomic m_instance;public:static Singleton* getInstance() {Singleton* t = m_instance.load(std::memory_order_relaxed);std::atomic_thread_fence(std::memory_order_acquire);if (t == nullptr) {std::unique_lock lock(m_mutex);t = m_instance.load(std::memory_order_relaxed);if (t == nullptr) {t = new Singleton();std::atomic_thread_fence(std::memory_order_release);m_instance.store(t, std::memory_order_relaxed);}}return t;}static void deleteInstance() {std::unique_lock lock(m_mutex);if (m_instance != nullptr) delete m_instance;m_instance = nullptr;}
};
std::atomic Singleton::m_instance;
std::mutex Singleton::m_mutex;int main () {Singleton* ps = Singleton::getInstance();Singleton::deleteInstance();return 0;
}