什麼是設計模式設計模式
設計模式表明了最佳實踐,是軟件開發過程當中面臨通常問題的解決方案。安全
設計模式是一套被反覆使用、通過分類、代碼設計總結的經驗。併發
單例模式ide
單例模式也叫單件模式。Singleton是一個很是經常使用的設計模式,幾乎全部稍微大一些的程序都會使用到它,因此構建一個線程安全而且高效的Singleton很重要。函數
1. 單例類保證全局只有一個惟一實例對象。高併發
2. 單例類提供獲取這個惟一實例的接口。優化
怎樣設計一個單例模式spa
實現一(不考慮線程安全)線程
class Singleton { public: // 獲取惟一對象實例的接口函數 static Singleton* GetInstance() { if(_sInstance == NUL) { if (_sInstance == NULL) { _sInstance = new Singleton(); } } return _sInstance; } // 刪除實例對象 static void DelInstance() { if (_sInstance) { delete _sInstance; _sInstance = NULL; } } void Print() { cout<<_data<<endl; } private: // 構造函數定義爲私有,限制只能在類內建立對象 Singleton() :_data(0) {} Singleton(const Singleton&); Singleton& operator=(const Singleton&); // 指向實例的指針定義爲靜態私有,這樣定義靜態成員函數獲取對象實例 static Singleton* _sInstance; // 單例類裏面的數據 int _data; }; Singleton* Singleton::_sInstance = NULL; void TestSingleton() { Singleton::GetInstance()->Print(); Singleton::DelInstance(); }
實現二設計
線程安全的單例 -- (懶漢模式-- lazy loading)
ps: 下面部分的加鎖使用了C++11庫的互斥鎖 class Singleton { public: // 獲取惟一對象實例的接口函數 static Singleton* GetInstance() { // 使用雙重檢查,提升效率,避免高併發場景下每次獲取實例對象都進行加鎖 if (_sInstance == NULL) { std::lock_guard<std::mutex> lck(_mtx); if (_sInstance == NULL) { // tmp = new Singleton()分爲如下三個部分 // 1.分配空間 2.調用構造函數 3.賦值 // 編譯器編譯優化可能會把2和3進行指令重排,這樣可能會致使 // 高併發場景下,其餘線程獲取到未調用構造函數初始化的對象 // 如下加入內存柵欄進行處理,防止編譯器重排柵欄後面的賦值 // 到內存柵欄以前 Singleton* tmp = new Singleton(); MemoryBarrier(); _sInstance = tmp; } } return _sInstance; } // 刪除實例對象 static void DelInstance() { std::lock_guard<std::mutex> lck(_mtx); if (_sInstance) { delete _sInstance; _sInstance = NULL; } } void Print() { cout<<_data<<endl; } private: // 構造函數定義爲私有,限制只能在類內建立對象 Singleton() :_data(0) {} Singleton(const Singleton&); Singleton& operator=(const Singleton&); // 指向實例的指針定義爲靜態私有,這樣定義靜態成員函數獲取對象實例 static Singleton* _sInstance; // 保證線程安全的互斥鎖 static mutex _mtx; // 單例類裏面的數據 int _data; }; Singleton* Singleton::_sInstance = NULL; mutex Singleton::_mtx; void TestSingleton() { Singleton::GetInstance()->Print(); Singleton::DelInstance(); }
實現三
線程安全的單例 -- (餓漢模式--簡潔、高效、不用加鎖、可是在某些場景下會有缺陷)
方法1
// 方式一 class Singleton { public: // 獲取惟一對象實例的接口函數 static Singleton* GetInstance() { static Singleton sInstance; return &sInstance; } void Print() { cout<<_data<<endl; } private: // 構造函數定義爲私有,限制只能在類內建立對象 Singleton() :_data(0) {} Singleton(const Singleton&); Singleton& operator=(const Singleton&); // 單例類裏面的數據 int _data; }; void TestSingleton() { Singleton::GetInstance()->Print(); }
方法2
// 方式二 class Singleton { public: // 獲取惟一對象實例的接口函數 static Singleton* GetInstance() { assert(_sInstance); return _sInstance; } // 刪除實例對象 static void DelInstance() { if (_sInstance) { delete _sInstance; _sInstance = NULL; } } void Print() { cout << _data << endl; } private: // 構造函數定義爲私有,限制只能在類內建立對象 Singleton() :_data(0) {} Singleton(const Singleton&); Singleton& operator=(const Singleton&); // 指向實例的指針定義爲靜態私有,這樣定義靜態成員函數獲取對象實例 static Singleton* _sInstance; // 單例類裏面的數據 int _data; }; Singleton* Singleton::_sInstance = new Singleton; void TestSingleton() { Singleton::GetInstance()->Print(); Singleton::DelInstance(); }
帶RAII GC 自動回收實例對象的方式
class Singleton { public: // 獲取惟一對象實例的接口函數 static Singleton* GetInstance() { assert(_sInstance); return _sInstance; } // 刪除實例對象 static void DelInstance() { if (_sInstance) { delete _sInstance; _sInstance = NULL; } } void Print() { cout << _data << endl; } class GC { public: ~GC() { cout << "DelInstance()"<<endl; DelInstance(); } }; private: // 構造函數定義爲私有,限制只能在類內建立對象 Singleton() :_data(0) {} // 指向實例的指針定義爲靜態私有,這樣定義靜態成員函數獲取對象實例 static Singleton* _sInstance; // 單例類裏面的數據 int _data; }; // 靜態對象在main函數以前初始化,這時只有主線程運行,因此是線程安全的。 Singleton* Singleton::_sInstance = new Singleton; // 使用RAII,定義全局的GC對象釋放對象實例 Singleton::GC gc; void TestSingleton() { Singleton::GetInstance()->Print(); }
因爲程序在結束的時候,系統會自動析構全部的全局變量,實際上,系統也會析構全部類的靜態成員變量,就像這些靜態變量是全局變量同樣。咱們知道,靜態變量和全局變量在內存中,都是存儲在靜態存儲區的,因此在析構時,是同等對待的。