單例模式,模式
1.優缺點
單利模式就是在一個jvm中只能存在一個執行個體(不考慮反射)這樣設計主要有兩方面好處:
1.從jvm來說,對於頻繁使用的對象,可以減去建立的時間(這對於重量級的對象,是非常客觀的開銷),由於new 對象的操作減少,對系統記憶體的使用頻率降低,將會減輕GC壓力,縮短GC停頓時間(摘自 java程式效能最佳化 --葛一鳴)。
2.從設計來講,某些執行個體一個系統中本應只存在一個(邏輯上),並且只對同一對象操作,能有效保證一致性(並發時可相應處理)。
同時也存在一些需要注意的問題:
1、由於單利模式中沒有抽象層,不利於擴充,所以很多責任都是自己扛,可能會導致單例類的職責過重,在一定程度上違背了“單一職責原則”。
2.如這個執行個體建立過程很慢而且不一定會用到,可能需要消極式載入。
3、濫用單例將帶來一些負面問題,如為了節省資源將資料庫連接池對象設計為的單例類,可能會導致共用串連池對象的程式過多而出現串連池溢出;如果執行個體化的對象長時間不被利用,系統會認為是垃圾而被回收,這將導致對象狀態的丟失。
2.實現方式
實現單例的根本是私人化構造器(在類內部建立對象),然後根據不同的情境設計擷取執行個體的方法,下面是幾種常見的實現方式。
1.餓漢式: 這種方式在類載入時就完成了初始化,所以類載入較慢,但擷取對象的速度快。 基於類載入機制實現可避免多線程的同步問題,但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化instance顯然沒有達到消極式載入的效果。
1 public class Singleton1 {//惡漢式2 private Singleton1(){}3 private static Singleton1 singleton = new Singleton1();4 public static Singleton1 getInstance(){5 return singleton;6 }7 }
2.懶漢式:可實現消極式載入,但是多線程下存在致命問題。
1 public class Singleton2 {//懶漢式 2 private Singleton2(){}; 3 private static Singleton2 singleton; 4 public static Singleton2 getInstance(){ 5 if(singleton == null){ 6 singleton = new Singleton2(); 7 } 8 return singleton; 9 }10 }
3.雙重檢查模式:改進的懶漢式。為解決線程同步問題,最簡單的方法是對getInstance方法整體加關鍵字synchronized,但是這種實現方式效率會至少低2個數量級。其中一種不錯的改進方式是雙重檢查模式(DCL),這種寫法在getSingleton方法中對singleton進行了兩次判空,第一次是為了不必要的同步,第二次是在singleton等於null的情況下才建立執行個體。在這裡用到了volatile關鍵字,在這裡使用volatile會或多或少的影響效能,但考慮到程式的正確性,犧牲這點效能還是值得的。DCL優點是資源使用率高,第一次執行getInstance時單例對象才被執行個體化,效率高。缺點是第一次載入時反應稍慢一些,在高並發環境下也有一定的缺陷(DCL失效),雖然發生的機率很小。
1 public class Singleton3 {//雙重檢查模式(DCL) 2 private Singleton3(){}; 3 private static volatile Singleton3 singleton; 4 public static Singleton3 getInstance(){ 5 if(singleton == null){ 6 synchronized(Singleton3.class){ 7 if(singleton == null){ 8 singleton = new Singleton3(); 9 }10 }11 }12 return singleton;13 }14 }
4.靜態內部類:既可以實現消極式載入,又不會有線程問題(推薦)
1 public class Singleton4 {//靜態內部類模式(DCL)2 private Singleton4(){};3 static class ClassHolder{4 private static Singleton4 singleton = new Singleton4();5 }6 public static Singleton4 getInstance(){7 return ClassHolder.singleton;8 }9 }
5.枚舉:這種方式是Effective java作者Josh Bloch提倡的方式,它不僅能避免多線程的同步問題,而且還能防止還原序列化重新建立對象的問題。聽起來很給力,不過工作中基本見過,有機會試試。
1 public enum Singleton5 {//枚舉模式2 INSTANCE;3 public void whateverMethod(){4 //同枚舉類使用(它本來就是枚舉!),不需要擷取執行個體的方法。5 }6 }
6.利用容器保證單例:在不考慮容器本身對並發的處理的情況下,這種方式能有效管理多種類型的單例,並且在使用時可以通過統一的介面進行擷取操作,降低了使用者的使用成本,也對使用者隱藏了具體實現,降低了耦合度。
1 //利用容器實現單例 2 //最好將你要裝載的單例物件建構函數私人化,這樣可以避免很多問題 3 // 如果你能保證每次都是從這個類載入器擷取對象,建構函式是否私人化毫不相干 4 public class SingletonManager { 5 private static Map<String, Object> objMap = new HashMap<String,Object>(); 6 public static void registerService(String key, Object instance) { 7 if (!objMap.containsKey(key) ) { 8 objMap.put(key, instance) ; 9 }10 }11 public static Object ObjectGetService(String key) {12 return objMap.get(key) ;13 }14 } 3.破壞單例的情況
1.反射:就當他不存在吧。
2.多個類載入器:例如一些servlet容器對每個servlet使用完全不同的類裝載器,這樣的話如果有兩個servlet訪問一個單例類,它們就都會有各自的執行個體。解決方案就是想辦法使用同一個類載入器(廢話)。
3.序列化複原:當你序列化複原一個單例對象時候,就會出現多個單例對象。如這樣:
1 public class Test { 2 public static void main(String[] args) throws IOException, ClassNotFoundException { 3 Singleton1 singleton = Singleton1.getInstance(); 4 //先將單例對象序列化到檔案 5 FileOutputStream outputStream = new FileOutputStream("E:Singleton.txt"); 6 ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); 7 objectOutputStream.writeObject(singleton); 8 objectOutputStream.flush(); 9 objectOutputStream.close();10 //從檔案讀取對象11 FileInputStream inputStream = new FileInputStream("E:Singleton.txt");12 ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);13 Singleton1 newSingleton = (Singleton1) objectInputStream.readObject();14 15 //test16 System.out.println(singleton == newSingleton); // false17 System.out.println(singleton == Singleton1.getInstance());// true18 }19 20 }
有效方法是在單例類中加入readResolve方法(序列化操作提供了一個很特別的鉤子(hook)類中具有一個私人的被執行個體化的方法readresolve(),這個方法可以確保類的開發人員在序列化將會返回怎樣的object上具有發言權)。例如:
1 public class Singleton1 implements Serializable{//惡漢式 2 private Singleton1(){} 3 private static Singleton1 singleton = new Singleton1(); 4 public static Singleton1 getInstance(){ 5 return singleton; 6 } 7 private Object readResolve(){ 8 return singleton; 9 }10 }
在運行上面的測試就會的到兩個true。
4.感謝:很多內容是從網上檢索整理得,感謝感謝!!