Singleton模式
创始人
2024-05-30 18:43:33

饿汉模式

饿汉模式的单例本身就是线程安全

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;
}

懒汉模式-naive实现

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;
}

相关内容

热门资讯

北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...