標籤:
辛格爾頓(Singleton)
一個、 什麼是單例模式
單例模式。簡單點來說就是設計一個類,使其在不論什麼時候,最多僅僅有一個執行個體,並提供一個訪問這個執行個體的全域訪問點。
二、 為什麼要單例
在程式中的非常多地方。僅僅有一個執行個體是非常重要的。比如,在windows中。工作管理員僅僅有一個。不管你點擊多少次開啟工作管理員,工作管理員也僅僅會產生一個表單。再比如,在一些軟體中,工具箱是唯一的,不管你點擊多少次開啟工具箱。工具箱也僅僅一個。
為什麼要這樣設計呢?由於像工作管理員或工具箱這種程式,僅僅要有一個就足夠完畢全部的工作了。多個程式僅僅會白白消耗系統資源,而像工作管理員這類的程式還會引入多個工作管理員之間的同步問題。所以對些這些程式來說。僅僅有一個執行個體或程式是必要的。
三、 為什麼須要單例模式
上面講到對於某些程式來說。保持其僅僅有一個執行個體是必要的,可是怎樣保證一個程式或一個類僅僅有一個執行個體呢?以下從類的角度來講解。
第一種方法。我們拋開設計模式這個概念,假設你之前全然不知道這個概念。面對這個設計要求你會怎樣做?我們能夠使用一個全域的類指標變數,初始值為NULL。每當須要建立該類的對象時,都檢查該指標是否為NULL。若為NULL,則使用new建立新的對象,並把對象的指標賦值給該全域指標變數。
若該指標不為NULL。則直接返回該指標或使用該指標。
這個可能是最easy想到的方法。
另外一種方法,就是使用單例模式。單例模式通過在類內維護一下指向該類的內部的指標,並把其建構函式聲明為private或protected來阻止一般的執行個體化。而使用一個static的公有成員函數來實現和控制類的執行個體化。
在該static公有成員函數中推斷該類的靜態成員指標是否為NULL,若為NULL。則建立一個新的執行個體。並把該類的靜態成員指標指向該實現。若該靜態成員指標不為NULL,則直接返回NULL。
若這裡看得不是非常明確,不要緊,看了以下的類圖和代碼自會明確。
相比之下,另外一種方法比第一種方法好在哪裡呢?首先。第一種做法並沒有強制一個類僅僅能有一個執行個體,一切的控制權事實上在使用者的設計中。而另外一種做法,則是類的設計者做好的,與使用者並沒有關係。
換句話來說,假設使用第一種做法,則僅僅要使用者願意。該類能夠有無數個執行個體,而對於另外一種方法。不管使用都是否願意,它僅僅能有一個執行個體。這就好比我們去吃飯,第一種方法須要顧客來推斷哪些菜已經賣完。而另外一種方法由餐館的推斷哪些菜已經賣完,顯然在生活中,另外一種方法才是合理的。
四、 單例模式的類圖
五、 單例模式的實現(C++實現)
1、singleton.h,定義類的基本成員及介面
#ifndef SINGLETON_H_INCLUDE#define SINGLETON_H_INCLUDE class Singleton{ public: static Singleton*getInstance(); voidreleaseInstance(); private://function Singleton(){} ~Singleton(){} private://data static Singleton*_instance; static unsigned int_used;};#endif
2、singleton.cpp。實現getInstance方法和releaseInstance方法
#include "singleton.h" Singleton* Singleton::_instance(0);unsigned int Singleton::_used(0); Singleton* Singleton::getInstance(){ ++_used; if(_instance == 0) { _instance = newSingleton(); } return _instance;} void Singleton::releaseInstance(){ --_used; if(_used == 0) { delete _instance; _instance = 0; }}
程式碼分析:
從上面的類圖和代碼實現能夠看到。在單例模式中。我們把類的建構函式聲明為私人的,從而阻止了在類外執行個體化對象。既然在類外不能執行個體化對象,那麼我們怎樣執行個體化該類呢?我們知道static成員是隨類而存在的。並不隨對象而存在,所以我們利用一個公有的static函數,由它來負責實現化該類的對象。由於該static函數是該類的成員函數,它能夠訪問該類的private的建構函式,它也就是我們之前所說的全域訪問點。
由於可能有多個對象都引用該單例類的對象,而該對象僅僅有一個,所以肯定會有多個指標變數指向堆中同一塊記憶體,若當中一個指標把該堆記憶體delete掉,然而其它的指標並不知道它所引用的對象已經不存在,繼續引用該對象必定會發生段錯誤,為了防止在類的外部調用delete。在這裡把解構函式聲明為private,從而讓在類外delete一個指向該單例類對象指標的操作非法。
可是C++的堆記憶體全然由程式猿來管理,假設不能delete的話。該對象就會在堆記憶體中一直存在,所以在此引入了一個方法releaseInstance和引用計數,當不再須要使用該對象時調用releaseInstance方法,該方法會把引用計數減1,當全部代碼都不須要使用該對象時釋放該對象,即當引用計數為0時,釋放該對象。
六、 多線程下的單例模式
上面的代碼在多線程環境下會引發問題。舉個範例。就是當兩個線程同一時候調用getInstance函數時。若該類還沒有被執行個體化,則兩個線程讀取到的_instance為0。那麼兩個線程都會new一個新的對象,從而讓該類有兩個執行個體。同一時候,對_used的操作也會存在相同的問題,此時_use為1。
所以,顯然該設計在多線程下是不安全的。
為瞭解決上述問題,我們須要為函數getInstance和releaseInstance中對_instance和_used的訪問加鎖。
為了簡便。僅僅列出部分關鍵代碼。改動後的代碼例如以下所看到的:(源碼檔案為singlton_thread.h和singleton_thread.cpp)
pthread_mutex_tSingleton::_mutex(PTHREAD_MUTEX_INITIALIZER); Singleton* Singleton::getInstance(){ pthread_mutex_lock(&_mutex); ++_used; if(_instance== 0) { _instance= new Singleton(); } pthread_mutex_unlock(&_mutex); return_instance;} void Singleton::releaseInstance(){ pthread_mutex_lock(&_mutex); --_used; if(_used== 0) { delete_instance; _instance= 0; } pthread_mutex_unlock(&_mutex);}
程式碼分析:
從上面的代碼能夠看出,每次申請調用get/releaseInstance函數都會加鎖和解鎖,而加鎖和解鎖都是比較耗時的操作,所以上述的代碼效率事實上並不高。
在一些設計模式的書上,會看到使用雙if的推斷來解決多次上鎖的問題,可是這種方法在這裡是不現實的,由於這種方法不能解決_used的訪問問題。也就是說。即使對_instance的訪問能夠使用雙if語句來大大降低加鎖和解鎖的操作。可是對_used的++和--操作相同須要加鎖進行。而那些書上之所以能夠使用雙if來解決問題。是由所使用的語言決定的,比如使用java或c#,它們不須要對記憶體進行管理,所以不會存在上面代碼中所出現的引用計數_used,所以雙if的方法才行得通。
若想在C++中實現雙if的推斷,則不使用引用計數來管理記憶體就可以。即對象一旦分配就一直存在於堆記憶體中。
此時C++也不存在引用計數問題。不須要釋放記憶體,因而也就不須要上面的releaseInstance方法。
其getInstance方法的實現例如以下:
Singleton* Singleton::getInstance(){if(_instance == 0){pthread_mutex_lock(&_mutex);if(_instance == 0)_instance = new Singleton();pthread_mutex_unlock(&_mutex);}return _instance;}
這樣就能夠僅僅加鎖和解鎖一次,大大提高時間效率。可是對象一旦分配記憶體,記憶體就不會被釋放。所以在C++中使用哪種實現策略,取決於你對時間和空間的取捨。若時間更重要。則採用後一種方法,若空間更重要,則採用前一種方法。
七、 Android中的單例模式
Android中存在著大量的單例類,如:InputMethodManager類,CalendarDatabaseHelper類、Editable類等等。在這些類中,都存在一個方法getInstance,在該方法或直接返回對象的引用或推斷一個類的引用是否為NULL。若不為NULL,則直接返回該引用,若為NULL,則new一個新的對象,並返回。比如。對於CalendarDatabaseHelper類,存在例如以下的代碼:
public static synchronized CalendarDatabaseHelper getInstance(Contextcontext){ if (sSingleton == null) { sSingleton = newCalendarDatabaseHelper(context); } return sSingleton;}
從這裡的代碼能夠看出,事實上現方式與上面所說的非常類似,只是由於java不用程式猿自己管理記憶體,所以並不須要使用引用計數,而該方法是公有static的。而synchronized就是為了保證同一時刻僅僅能有一個線程進入該方法,這也就是防止上面第六點講到的單例模式在多線程中的安全問題。
八、 源碼地址
C++源碼地址:http://download.csdn.net/detail/ljianhui/7464147
著作權聲明:本文部落格原創文章。部落格,未經同意,不得轉載。
辛格爾頓和Android