分類: Java
在某些應用的場合,一個或者某些類只需要或者只能存在一個實例,這種情況下單例模式便派上了用場。在windows操作系統中,垃圾回收站就是典型的應用,還有很多工程中的配置文件等等,系統中只需要一個實例。當系統需要該類實例時,如果系統中存在該類的實例直接返回實例。如果系統沒有該類實例時,創建一個新的類實例,從而保證系統中只存在該對象的一個實例。
系統中類實例創建主要由於其構造方法來完成的,因此在這個時候就需要控制類的初始化過程。下面有兩個例子,用不同方式產生所需類的單一實例。
1: 餓漢式
2: public class EagerSingletonPattern {
3:
4: private static final EagerSingletonPattern ins = new EagerSingletonPattern();
5:
6: private EagerSingletonPattern(){
7: }
8:
9: public static EagerSingletonPattern getInstance(){
10: return ins;
11: }
12: }
下面便是懶漢式:
1: 懶漢式
2: public class LazySingletonPattern {
3: private static LazySingletonPattern ins = null;
4:
5: private LazySingletonPattern(){
6: }
7:
8: public synchronized static LazySingletonPattern getInstance(){
9: if( ins == null){
10: ins = new LazySingletonPattern();
11: }
12: return ins;
13: }
14: }
餓漢式在類加載的時候便初始化該類的一個實例,系統需要該類實例時直接返回。在系統調用時響應速度較快,但系統資源利用率較低。懶漢式在需要類實例的時候,先檢查系統中是否已經存在類實例。如果存在直接返回,反之創建一個實例。系統資源利用率較高,但在系統調用時響應速度較餓漢式要慢,而且在多線程中,需要線程之間的同步,有可能在資源初始化的時候耗時比較長。
這2種實現方式基本上可以實現所需的要求。但是在懶漢式實現中,後續線程需要等待執行方法線程完成之後才能進入執行,無疑,這延長了系統響應時間。在《Head First 設計模式》書中提到了對懶漢式的一種改進,我個人認爲這種方法是正確的,儘管在《Java與模式》書中提到雙重檢查成例的不正確性。下面我給出《Head First 設計模式》中雙重檢查加鎖的例子。
1: public class DCLSingletonPattern {
2:
3: private volatile static DCLSingletonPattern ins= null;
4:
5: private DCLSingletonPattern(){}
6:
7: public static DCLSingletonPattern getInstance(){
8: if(ins == null){ //第一重檢查
9: synchronized(DCLSingletonPattern.class){//線程同步
10: if(ins == null){ //第二重檢查
11: ins = new DCLSingletonPattern();
12: }
13: }
14: }
15: return ins;
16: }
17: }
volatile關鍵詞確保變量ins在初始化爲實例後,多線程能正確的處理ins變量。
上面給出的例子都或多或少都存在一些缺陷,它們的通病都在於其不能被子類所繼承。如果能被子類所繼承的話,其構造方法至少爲protect,而不能爲private。但是如果修改成protect,該類就失去了單例模式的特性--其他類可以直接調用其構造方法產生新的實例。因此,在《Java與模式》中給出了登記式的實現方法。但是子類構造方法也不能爲private,其構造方法對外開放或部分開放,只能按照登記式實例化可以產生單例,因而也並不完美。在此,我就不給這個例子了,實際應用中,懶漢式和餓漢式基本上夠用。