單利模式就是在一個jvm中只能存在一個實例(不考慮反射)這樣設計主要有兩方面好處:
1.從jvm來說,對於頻繁使用的對象,可以減去創建的時間(這對於重量級的對象,是非常客觀的開銷),由於new 對象的操作減少,對系統內存的使用頻率降低,將會減輕GC壓力,縮短GC停頓時間(摘自 java程序性能優化 --葛一鳴)。
2.從設計來講,某些實例一個系統中本應只存在一個(邏輯上),並且只對同一對象操作,能有效的保證一致性(並發時可相應處理)。
同時也存在一些需要注意的問題:
1、由於單利模式中沒有抽象層,不利於擴展,所以很多責任都是自己扛,可能會導致單例類的職責過重,在一定程度上違背了“單一職責原則”。
2.如這個實例創建過程很慢而且不一定會用到,可能需要延遲加載。
3、濫用單例將帶來一些負面問題,如為了節省資源將數據庫連接池對象設計為的單例類,可能會導致共享連接池對象的程序過多而出現連接池溢出;如果實例化的對象長時間不被利用,系統會認為是垃圾而被回收,這將導致對象狀態的丟失。
實現單例的根本是私有化構造器(在類內部創建對象),然後根據不同的場景設計獲取實例的方法,下面是幾種常見的實現方式。
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 }
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 //test 16 System.out.println(singleton == newSingleton); // false 17 System.out.println(singleton == Singleton1.getInstance());// true 18 } 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。