設計模式筆記—10_Singleton單件模式

Singleton單件模式是一種“對象性能”模式

  • 面向對象很好地解決了“抽象”的問題,但是必不可免地要付出一定的代價。對於通常情況來說,面向對象的成本大都可以忽略不計。但是某些情況,面向對象所帶來的成本必須謹慎處理

動機

  • 在軟件系統中,經常有這樣一些特殊的類,必須保證它們在系統中只存在一個實例,才能確保它們的邏輯正確性、以及良好的效率
  • 如何繞過常規的構造器,提供一種機制來保證一個類只有一個實例?
  • 這應該是類設計者的責任,而不是使用者的責任

定義

  • 保證一個類僅有一個實例,並提供一個該實例的全局訪問點

結構

代碼對比

Singleton.cpp


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









//雙檢查鎖,但由於內存讀寫reorder不安全
Singleton* Singleton::getInstance() {
    
    if(m_instance==nullptr){
        Lock lock;
        if (m_instance == nullptr) {
            m_instance = new Singleton();	//先分配內存,再調用構造器,把指針的返回值(內存地址)給m_instance。這三步有可能reorder(由123變成132)
        }
    }
    return m_instance;
}








//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單件模式中單件類要把構造函數和拷貝構造函數設置爲private
  • 線程安全版本下鎖的代價過高(都是讀取操作的話會一直等待),尤其在高併發狀態下
  • 50行雙檢查鎖下可能由於內存讀寫reorder安全
    • 先分配內存,再調用構造器,把指針的返回值(內存地址)給m_instance。這三步有可能reorder(由123變成132)
  • 高級語言使用volatile關鍵字保證編譯器在編譯時不會reorder
    • 或者可以使用static關鍵字

要點總結

  • Singleton模式中的實例構造器可以設置爲protected以允許子類派生
  • Singleton模式一般不要支持拷貝構造函數和Clone接口,因爲這樣有可能導致多個對象實例,與Singleton模式的初衷違背
  • 如何實現多線程環境下安全的Singleton?注意對雙檢查鎖的正確實現
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章