1. 程式人生 > >設計模式複習篇——單例模式

設計模式複習篇——單例模式

單例模式的的使用場景:建立比較耗資源的、全域性呼叫的類。

單例特點:
1.構造方法私有化。
2.有一個靜態的方法用來獲取物件或者一個靜態物件。
3.執行緒安全,確保單例類物件有且只有一個,尤其在多執行緒環境下。
4.確保反序列化時不會重構物件

六種單例模式的實現:
1.餓漢模式

public class Singleton{ 
 	private static Singleton instance = new Singleton(); 
 	private Singleton(){}
} 

優點:類一載入,類的物件就開始初始化,減少了重複物件建立銷燬
缺點:同樣,這樣會造成一定的浪費,並且執行緒不安全。
名字由來:不管你用不用一載入類就例項化物件。

2.懶漢模式

public class Singleton{ 
	 private static Singleton instance = null; 
	 private Singleton(){}
	 public static synchronized Singleton getInstance(){
	  	if(instance == null){
	  		 instance = new Singleton();
	  	}
	  return instance;
	 }
} 

優點:物件在第一次呼叫時進行初始化,有一定程度的節約記憶體,執行緒安全。
缺點:第一次載入需要例項化,反應稍慢;synchronize這個同步操作導致每次呼叫這個getInstance方法的時候會消耗一部分資源。
名字由來:在你需要呼叫的時候才開始例項化物件。

3.Double Check Look(DCL)雙重鎖檢查

 public class Singleton{ 
 	private static Singleton instance = null; 
 	private Singleton(){}
  	public static Singleton getInstance(){
   		 if(instance == null){                    //第一次檢查,作用減少synchonsized使用   
	  		 (synchronsized.class){
		    		if(instance == null){             //第二次檢查,保證單例
		    			 instance = new Singleton();
 		   		}
		   	}
	  	}
	   return Instance;
	 }
} 

優點:它相比懶漢模式不僅執行緒安全,並且它多了一次判斷,如果不是第一次建立就不走同步程式碼,這樣減少了效能消耗。
缺點:在JDK1.5由於java處理器採用亂序執行," Instance = new Singleton; "這句並不是原子語句(不可拆分語句就是原子語句),它分三個步驟①給物件分配記憶體空間②初始化物件③物件指向給分配的記憶體空間地址。它的②③語句在JDK5是不分順序的,所以有可能我們的程序執行了①③後被中斷執行其他地方了,然後用到這個物件,但此時物件雖然不為null但是並沒用進行初始化,所以這也是不安全的。(JDK6對這個進行了改正,具體化了volatile,使用public static volatile Singleton Instance = null解決了這個問題,它保證了每次從記憶體中獲取這個物件)。

4.靜態內部類

 public class Singleton{ 
 	public static Singleton Instance = null; 
 	 private Singleton(){}
	  public static Singleton getInstance(){
	  	 return SingletonHolder.mInstance;
	  }
	  static  class SingletonHolder{
	  	public static Singleton mInstance = new Singleton(); 
	  }
} 

優點:getInstance()在第一次呼叫時初始化物件,解決了DCL失效問題(上面的JDK5DCL存在的問題)。

5.利用容器實現

 public class SingletonManger{
 	private static HashMap<String, Object> objMap = new HashMap<String, Object>(); 
 	private SingletonManger(){}
	 public static registerService(String key, Object instance){
	 	 if(!objMap.containKey(key)){
  	 		objMap.put(key ,instance);
		  }
 	}
 	public static Object getService(String key){
 		 return objMap.get(key);
 	}
}

優點:可以管理多種單例,並且隱藏了具體實現,降低了耦合度。

6.列舉

 public enum SinglonEnum{
	 INSTANCE;
 	public void doSomething(){
		  System.out.plintln("do thing");
	 }
}

優點:寫法簡單,並且預設列舉例項的建立是執行緒安全的,並且任何情況下他都是一個單例,並且它很巧妙地避免了反射攻擊。

在上述五種方法(除了列舉)中需要注意這一點,防止物件多次建立物件,要杜絕單例物件在反序列化時重新生成物件必須加入如下方法:
private Object readResolve() throws ObjectStreamException{
return sInstence;
}
因為序列化可以將一個單例的例項物件寫到磁碟,然後再讀回來,(前面經過了一個讀寫操作然後就不安全了!)雖然單例模式構造器私有了,但反序列化會通過特殊途徑相去建立這個類的一個新的例項,相當於呼叫該類的構造器。上面這個方法可以使開發人員控制物件的反序列化,將這個方法寫成上面那樣就可以直接返回這個物件,而不是重新生成一個新的物件。
但列舉不存在這個問題,因為即使反序列化它也不會生成新的物件。
關於列舉和反序列化問題我覺得這個文章講的十分清楚了:為什麼要用列舉實現單例模式(避免反射、序列化問題)

總結:
優點:
1.由於單例模式在記憶體中只存在一個例項,減少了開支,特別是一個物件頻繁的建立銷燬,而且建立銷燬時效能無法優化,單例模式的優點就十分明顯。
2.由於單例模式只生成一個例項,減少了系統性能開銷,當一個物件的乘勝需要比較多的資源的時,如讀取配置、產生其他依賴物件時則可以通過應用啟動時直接生產一個單例物件,然後用永久駐留記憶體的方式來解決。
3.單例模式可以避免對資源的多重佔用,例如一個寫檔案操作,由於只有一個實力存在,避免對同一個資原始檔的同時寫操作。
4.單例模式可以在系統設定全全域性的訪問點,優化和共享資源訪問,例如,可以設計一個單例類,負責所有資料表的對映處理。

缺點:
1.單例模式一般沒有介面,擴充套件很困難,若要擴充套件,除了修改程式碼基本無其他辦法。
2.如果你的單例物件持有Activity的context引用需要注意記憶體洩漏,最好使用ApplicationContext。