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