設計模式複習篇——單例模式
單例模式的的使用場景:建立比較耗資源的、全域性呼叫的類。
單例特點:
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。