Singleton單例模式
1 動機
(1)在軟件系統中,經常有這樣一些特殊的類,必須保證他們在系統中只存在一個實例,才能確保他們的邏輯正確性,以及良好的效率。
(2)如何繞過常規的構造器,提供一種機制來保證一個類只有一個實例?
注:工廠模式之類的是繞過new,來解決緊耦合問題;單例模式是繞過常規的構造器,解決性能問題
(3)這應該是類設計者的責任,而不是使用者的責任。
2 模式定義
保證一個類僅有一個實例,並提供一個該實例的全局訪問點。
3 結構
4 僞代碼
class Singleton{
private://不寫這個構造和拷貝構造不行,編譯器會默認有缺省的共有的構造和拷貝構造
//讓外界不能用就設置私有
Singleton();
Singleton(const Singleton& other);
Singleton&operator=(const Singleton&);
public:
static Singleton* getInstance();//靜態函數
static Singleton* m_instance;//靜態變量
};
Singleton* Singleton::m_instance=nullptr;//設置成nullptr確認這是一個堆對象
//線程非安全版本(多線程不安全)
Singleton* Singleton::getInstance() {
if (m_instance == nullptr) {//多線程可能同時進來,那麼就會創建多個m_instance對象
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();//常規默認:1分配內存;2調用構造器;3賦值給m_instance。(編譯器可能會出現重排列)
}
}
return m_instance;
}
//加入volatile,那麼編譯器就會根據常規步驟進行,而不會對代碼進行優化
//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(柵欄屏蔽)//這兩步屏蔽編譯器的reorder
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;
}
5 要點總結
(1)Singleton模式中的實例構造器可以設置爲protected以允許子類派生。
(2)Singleton模式一般不要支持拷貝構造函數的Clone接口,因爲這有可能導致多個對象實例,與Singleton模式的初衷違背。
(3)如何實現多線程環境下安全的Singleton?注意對雙檢查鎖的正確實現。