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