對象性能模式之單例模式(Singleton)

一、概念

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

二、動機

  在軟件系統中,經常有這樣一個特殊的類,必須保證它們在系統中只存在一個示例,才能確保他們的邏輯正確性、以及良好的效率。如何繞過常規的構造器,提供一種機制來保證一個類只有一個實例?工廠模式繞過new是爲了避開緊耦合,單例模式避開new,是解決性能問題這個應該類設計者的責任,而不是使用者的責任。解決方案:

  • 將構造函數設置爲私有的;

  • 提供一個全局的靜態方法;

  • 定義一個靜態指針,指向本類的變量的靜態變量指針。

三、單例模式的實現

class Singleton {
private:
    Singleton();
    Singleton(const Singleton& other);
public:
    static Singleton* getInstance(); // 靜態成員是類的成員而不是對象的成員
    static Singleton* m_instance;
};

// 靜態成員需要在類外初始化
Singleton* Singleton::m_instance = nullptr;

// 線程非安全版本
Singleton* Singleton::getInstance() { // 由於是在類的外部定義,也是靜態方法,不用加static
    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) {
        	// 分配內存->調用構造函數->賦值,但是編譯器可能對這一過程進行優化,所以內存讀寫reorder不安全
            m_instance = new Singleton(); 
        }
    }
    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); // 屏蔽編譯器reorder
    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?注意對雙檢查鎖的正確實現

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