Singleton 单件模式

概述( overview )

面向对象很好的解决了抽象的问题,但是不可避免的要付出一定的代价。对于通常情况来讲,面向对象的成本大都可以忽略不记。但是某些情况下面向对象带来的成本必须谨慎处理。

动机( Motivation )

在软件系统里面,经常一些特殊的类,必须保证它们所在的系统中只存在一个实例,才能确保他们的逻辑正确性,以及良好的效率。如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例。这应该由设计者来完成而不是使用者来完成。

代码设计 (Code Design) 

 在设计的时候要将 构造函数和拷贝构造函数 不能设置成 Public 的(不设置的话对象生成会自动提供)。  

class Singleton{
private:
    Singleton();
    Singleton(const Singleton& other);
public:
    static Singleton* getInstance();
    static Singleton* m_instance;
};

适用单线程的

Singleton* Singleton::m_instance=nullptr;

//线程非安全版本
Singleton* Singleton::getInstance() {
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}

适用多线程的 


//线程安全版本,但锁的代价过高
Singleton* Singleton::getInstance() {
    Lock lock;
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}

对于读事件其实并没有必要加锁,在高并发(比如十几万人同时访问的 web),锁的代价就出现了。可以采用双锁来改进 只有为空的时候才进行加锁。

 解决高并发的方案


//双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance() {
    
    if(m_instance==nullptr){
        Lock lock;
        if (m_instance == nullptr) {
            m_instance = new Singleton();
        }
    }
    return m_instance;
}

 虽然双锁的方法看似解决了问题,但是编译器的优化会欺骗代码,导致双锁失效。

理论上:执行 m_instance = new Singleton();(我们所期望的)要以下的几个步骤

  1. 分配内存
  2. 调用构造器,对内存进行初始化
  3. 把指向那块内存的指针返回给m_instance

但是编译器很可能会把 2,3 调换,这样就会出现问题了。

reorder之后,先分配内存,然后把内存分配给m_instance,此时m_instance就不是null了。一个线程进来发现m_instance不是null,就直接执行

 return m_instance;

获取了对象实例,但是此时还没执行构造器,所以线程此时获取的是一块没被赋值的原生内存,这会导致错误发生。

新的解决方案(C++)

//C++ 11版本之后的跨平台实现 (volatile)
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
    Singleton* tmp = m_instance.load(std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
    if (tmp == nullptr) {
        std::lock_guard<std::mutex> lock(m_mutex);
        tmp = m_instance.load(std::memory_order_relaxed);
        if (tmp == nullptr) {
            tmp = new Singleton;
            std::atomic_thread_fence(std::memory_order_release);//释放内存fence
            m_instance.store(tmp, std::memory_order_relaxed);
        }
    }
    return tmp;
}

 

总结

  • Singleton模式中的实例构造器可以设置为protected以允许子类派生。
  • Singleton模式一般不要 支持拷贝构造函数和Clone接口, 因为这有可能导致多个对象实例,与Singleton模式的初衷违背。
  • 如何实现多线程环境下安全的Singleton ?注意对双检查锁的正确实现。

 

 写在最后的话

昨天是520,下面的话送给自己共勉

愿你身边有一个一直陪着你的人,ta 话不多,却让你感觉非常美好。

展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客
应支付0元
点击重新获取
扫码支付

支付成功即可阅读