單例模式是最簡單的設計模式,就讓我像玩簡單的遊戲同樣寫下去吧。程序員
v1: 簡單模式設計模式
和這個版本有過一面之緣,但不敢苟同。安全
class Singleton { private: Singleton() {} public: static Singleton * getIns() { static Singleton * ins = new Singleton(); return ins; } };
問題:什麼時候析構不明確;最重要的是調用屢次getIns函數會產生多個static Singleton指針,指向每次都調用都new出來的實例。多線程
v2: 通常模式函數
典型寫法學習
class Singleton { private: Singleton() {} static Singleton * ins; public: static Singleton * getIns() { if(!ins) ins = new Singleton(); return ins; } }; static Singleton * Singleton::ins = NULL;
問題:仍然未考慮析構問題;對象可能被複製出多個副本。測試
Java中因爲容許在調用構造函數以前先初始化變量,所以有這樣一種寫法:spa
public class Singleton { private Singleton() {} public static Singleton ins = new Singleton(); public static Singleton * getIns() { return ins; } };
簡潔明瞭,也是蠻OK啦,析構也省了,而且因爲初始化這個語句是JVM作的,所以人工的同步也省了(不帶這麼欺負C++程序員的 = =)。線程
v3: 增強模式設計
加入私有的複製構造函數以防出現單例對象的副本;加入一個內部靜態類,整個程序結束後,靜態類隨着其餘靜態變量消亡,此時調用析構函數將ins析構。
class Singleton { private: Singleton() {} Singleton(const Singleton & s) {} Singleton & operator = (const Singleton & s) {} static Singleton * ins; public: static Singleton * getIns() { if(!ins) ins = new Singleton(); return ins; } class CGarbo // 內部類 { public: ~CGarbo() { if(Singleton::ins) delete Singleton::ins; } }; static CGarbo Garbo; }; static Singleton * Singleton::ins = NULL;
問題:不是線程安全的。
v4: hard模式
static Singleton * getIns() { pthread_mutex_lock( &mutex_lock ); if(!ins) ins = new Singleton(); pthread_mutex_unlock( &mutex_lock ); return ins; }
問題:無論ins是否是空,都要加鎖,代價高
v5: 再接再勵模式
當ins不爲空時,反正getIns函數無論讀寫,直接返回就行了,讀寫鎖儘可交給使用者。
static Singleton * getIns() { if(!ins) { pthread_mutex_lock( &mutex_lock ); if(!ins) ins = new Singleton(); pthread_mutex_unlock( &mutex_lock ); } return ins; }
天然,getIns加了鎖,析構的時候也一樣要加鎖。繼續學習,再接再勵!
v6:虎牢關模式
實際上,只有在第一次建立單例的時候須要得到鎖。既然能夠用一個內部類的靜態實例實現單例對象的自動釋放,何不把該靜態對象的初始化也交由該內部類來維護呢:
class Singleton { public: Singleton() {} Singleton(const Singleton &s) {} Singleton & operator = (const Singleton & s) {} public: static Singleton *getInstance() { return GC::pinstance; } class GC { public: static Singleton *pinstance; GC() { pinstance = new Singleton(); } ~GC() { if( pinstance ) { delete pinstance; } } }; GC gc; }; Singleton * Singleton::GC::pinstance = nullptr;
注意,此處將Singleton的構造函數改爲了public。由於在系統中沒有Singleton實例時,Singleton的static成員也是不會被建立的,所以需將構造函數改爲public,在使用詞單例類時,先聲明一個Singleton對象。
如此,進程啓動,在初始化階段先初始化了不屬於任何實例對象的Singleton的各個靜態成員,順便new好了咱們所需的單例對象指針,這一過程不是線程來乾的,所以這樣的new操做是安全的。而在程序運行結束時,gc自動析構,將單例對象釋放掉,這一過程也不是線程來乾的,所以也是線程安全的,そして,至於pthread_mutex? WTF。而因爲無論存在多少Singleton對象,每次getInstance必然獲取到的是同一個instance。
v7: 終極模式
等等,全部「非你莫屬」的類,都要改寫成單例模式麼?NONONO,能不能只寫一個單例模式,全部須要限制實例個數的類均可複用之?這樣,當系統中須要多個不一樣類的單例對象時,能夠節約許多代碼。固然能夠作到,用模板就能夠:
template <typename T> class Singleton { public: Singleton() {} Singleton(const Singleton &s) {} Singleton & operator = (const Singleton & s) {} public: static Singleton *getInstance() { return GC::pinstance; } class GC { public: static T *pinstance; GC() { pinstance = new T(); } ~GC() { if( pinstance ) { delete pinstance; pinstance = nullptr; } } }; GC gc; };
template <typename T> T * Singleton<T>::GC::pinstance = nullptr;
固然很顯然,使用了模板的話,就須要一些約定俗成的東西了,例如在實例化的時候須要一個不帶參數的構造函數T()。在使用時,對於用Singleton<T>::getInstance函數返回獲得的對象指針,就是非你莫屬的同一個指針了:
int main() {
Singleton<int> sgton; int *p1 = Singleton<int>::getInstance(); int *p2 = Singleton<int>::getInstance(); int *p3 = Singleton<int>::getInstance();
printf("%x\n", p1); cout <<(p1==p2)<<endl; cout <<(p2==p3)<<endl; return 0; }
以上測試代碼的結果爲0x........ 1 1.
ちょっと待って!!還不夠!以上代碼仍是有問題的!注意此時咱們要限制對象個數的不是Singleton對象了,而是T對象。所以設計目標是:無論申明多少個Singleton對象都是合法的,而無論申明瞭多少個Singleton對象,調用getInstance都只會獲得一個T對象。
再看上面的代碼,若是用戶手賤聲明瞭多個Singleton對象會怎麼樣呢?對於一個Singleton類的static對象,只要程序中的Singleton對象個數大於0,就會維護惟一的一個static變量。但是上面的static變量是個指針!!這就意味着每次構造Singleton對象時,都會調用GC構造函數中的new運算符,將一個新的T對象的地址賦值給pinstance!!那舊的那個呢?WHO CARES!!這樣一來輕則致使內存泄漏,重則致使系統運行出錯——若是先申明瞭一個Singleton對象,用着用着中間又聲明出來一個Singleton對象,那麼pinstance指向的內容就被偷換了!
說了這麼多,爲了防止發生這樣的事情,只須要在new的時候和delete的時候稍稍注意一下便可,爲了防止new兩次,加上if語句;爲了防止delete兩次(固然在其餘場合也同樣),養成delete以後就將指針賦爲空,以避免指針在其餘地方被誤引用。
template <typename T> class Singleton { public: Singleton() {} Singleton(const Singleton &s) {} Singleton & operator = (const Singleton & s) {} public: static T *getInstance() { return GC::pinstance; } class GC { public: static T *pinstance; GC() { if( !pinstance ) pinstance = new T(); } ~GC() { if( pinstance ) { delete pinstance; pinstance = nullptr;
} } }; static GC gc; }; template <typename T> T * Singleton<T>::GC::pinstance = nullptr;
這樣一來,只要在起子線程以前聲明一個Singleton對象,那麼無論後面是main線程仍是子線程聲明瞭其餘的Singleton對象,T對象都不會出現第二個。
反思:
1 爲何Java用內部類維護單例?Java這麼作是爲了在不寫synchronized關鍵字(減小鎖的代價)的狀況下,採用延時加載的方式實現單例。怎麼作到不用鎖而作到線程安全呢?在Java中,內部類都只會加載一次,所以內部類的初始化過程有且僅有一次,而這個過程是JVM乾的,不受用戶多線程的影響。爲何是延時加載呢?只有getInstance()用到了內部類的時候,纔會加載內部類。
2 爲何C++寫法和Java延時加載且線程安全的單例寫法不同呢?C++是不會加載內部類的,只有當有對象須要實例化時,才調用其構造函數進行初始化,同時也初始化內部類的static變量。若是像Java同樣只是定義一個內部類而不去實例化它,是不行的。所以按照上面的寫法,使用該單例模板類必須先定義一個Singleton對象,這時候因爲內部類做爲Singleton的成員,會先被構造,「順便地」就正確地new出了一個T對象,pinstance指針是static變量,一個類最多隻有一個,因此不會有兩個pinstance,那麼只要再內部類的構造中防止new出兩個T對象,就能夠保證進程中有惟一的pinstance指向惟一的T。
to be continue