設計模式筆記12:單例模式

Singleton


"對象性能"模式

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

典型模式:

  • SIngleton
  • Flyweight

動機

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

模式定義

保證一個類僅有一個實例,並提供一個該實例的全局訪問點。 ——《設計模式》GoF


要點總結

  • 實例構造器可以設置爲protected以允許子類派生
  • 一般不要支持拷貝構造函數和Clone接口,因爲這有可能導致多個對象實例,與Singleton模式的初中違背。
  • 如何實現多線程環境下安全的Singleton?注意對雙檢查鎖的正確實現

Demo

Singleton.cpp:

class Singleton{
//1.設置構造函數、拷貝構造函數爲私有
private:
    Singleton();
    Singleton(const Singleton& other);
//2.設置靜態變量與靜態獲取方法
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不安全
//預想的new過程:malloc --> 調用構造函數 --> 返回對象指針
//編譯器有可能進行優化,reorder後的new過程:malloc --> 返回對象指針 --> 調用構造函數
//m_instance只是一個內存地址,沒有經過構造器,不加volatile不能用
Singleton* Singleton::getInstance() {
    
    if(m_instance == nullptr){ //假如單例已經創建,多個線程同時讀該變量,則可不需加鎖;都是讀操作時不需要加鎖的
        Lock lock;
        if (m_instance == nullptr) {
            m_instance = new Singleton();
        }
    }
    return m_instance;
}



//C++ 11版本之後的跨平臺實現 (volatile,只有VSC++可以使用,防止編譯器優化)
//也可以使用懶漢模式,不用這麼多代碼
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;
}

結構

在這裏插入圖片描述


懶漢式和餓漢式區別

所謂餓漢式,就是直接創建出類的實例化;
而對於懶漢式,就是在需要的時候再創建類的實例化
本文爲懶漢式單例模式,具體差異請參考: 單例模式(懶漢式和餓漢式區別)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章