只須要某個類同時保留一個對象,不但願有更多對象,此時,咱們則應考慮單例模式的設計。java
單例模式的主要做用是保證在Java程序中,某個類只有一個實例存在。安全
單例模式有不少好處,它可以避免實例對象的重複建立,不只能夠減小每次建立對象的時間開銷,還能夠節約內存空間;session
可以避免因爲操做多個實例致使的邏輯錯誤。若是一個對象有可能貫穿整個應用程序,並且起到了全局統一管理控制的做用,那麼單例模式也許是一個值得考慮的選擇。多線程
1. 單例模式只能有一個實例。併發
2. 單例類必須建立本身的惟一實例。ide
3. 單例類必須向其餘對象提供這一實例。函數
在知道了什麼是單例模式後,我想你必定會想到靜態類,「既然只使用一個對象,爲什麼不乾脆使用靜態類?」,這裏我會將單例模式和靜態類進行一個比較。測試
1. 單例能夠繼承和被繼承,方法能夠被override,而靜態方法不能夠。spa
2. 靜態方法中產生的對象會在執行後被釋放,進而被GC清理,不會一直存在於內存中。線程
3. 靜態類會在第一次運行時初始化,單例模式能夠有其餘的選擇,便可以延遲加載。
4. 基於2, 3條,因爲單例對象每每存在於DAO層(例如sessionFactory),若是反覆的初始化和釋放,則會佔用不少資源,而使用單例模式將其常駐於內存能夠更加節約資源。
5. 靜態方法有更高的訪問效率。
6. 單例模式很容易被測試。
誤解一:靜態方法常駐內存而實例方法不是。
實際上,特殊編寫的實例方法能夠常駐內存,而靜態方法須要不斷初始化和釋放。
誤解二:靜態方法在堆(heap)上,實例方法在棧(stack)上。
實際上,都是加載到特殊的不可寫的代碼內存區域中。
情景一:不須要維持任何狀態,僅僅用於全局訪問,此時更適合使用靜態類。
情景二:須要維持一些特定的狀態,此時更適合使用單例模式。
public class Singleton{ private static Singleton instance = new Singleton(); private Singleton(){} public static Singleton newInstance(){ return instance; } }
註解:初試化靜態的instance建立一次。若是咱們在Singleton類裏面寫一個靜態的方法不須要建立實例,它仍然會早早的建立一次實例。而下降內存的使用率。
缺點:沒有lazy loading的效果,從而下降內存的使用率。
public class Singleton{ private static Singleton instance = null; private Singleton(){} public static Singleton newInstance(){ if(null == instance){ instance = new Singleton(); } return instance; } }
註解:Singleton的靜態屬性instance中,只有instance爲null的時候才建立一個實例,構造函數私有,確保每次都只建立一個,避免重複建立。
缺點:只在單線程的狀況下正常運行,在多線程的狀況下,就會出問題。例如:當兩個線程同時運行到判斷instance是否爲空的if語句,而且instance確實沒有建立好時,那麼兩個線程都會建立一個實例,所以須要加鎖解決線程同步問題,實現以下:
public class Singleton{ private static Singleton instance = null; private Singleton(){} public static synchronized Singleton newInstance(){ if(null == instance){ instance = new Singleton(); } return instance; } }
public class Singleton { private static Singleton instance = null; private Singleton(){} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) {//2 instance = new Singleton(); } } } return instance; } }
註解:只有當instance爲null時,須要獲取同步鎖,建立一次實例。當實例被建立,則無需試圖加鎖。
缺點:用雙重if判斷,複雜,容易出錯。
public class Singleton{ private static class SingletonHolder{ public static Singleton instance = new Singleton(); } private Singleton(){} public static Singleton newInstance(){ return SingletonHolder.instance; } }
這種方式一樣利用了類加載機制來保證只建立一個instance實例。它與餓漢模式同樣,也是利用了類加載機制,所以不存在多線程併發的問題。不同的是,它是在內部類裏面去建立對象實例。這樣的話,只要應用中不使用內部類,JVM就不會去加載這個單例類,也就不會建立單例對象,從而實現懶漢式的延遲加載。也就是說這種方式能夠同時保證延遲加載和線程安全。
public enum Singleton{ instance; public void whateverMethod(){} }
上面提到的四種實現單例的方式都有共同的缺點:
1)須要額外的工做來實現序列化,不然每次反序列化一個序列化的對象時都會建立一個新的實例。
2)可使用反射強行調用私有構造器(若是要避免這種狀況,能夠修改構造器,讓它在建立第二個實例的時候拋異常)。
而枚舉類很好的解決了這兩個問題,使用枚舉除了線程安全和防止反射調用構造器以外,還提供了自動序列化機制,防止反序列化的時候建立新的對象。所以,《Effective Java》做者推薦使用的方法。不過,在實際工做中,不多看見有人這麼寫。
本文總結了五種Java中實現單例的方法,其中前兩種都不夠完美,雙重校驗鎖和靜態內部類的方式能夠解決大部分問題,平時工做中使用的最多的也是這兩種方式。枚舉方式雖然很完美的解決了各類問題,可是這種寫法多少讓人感受有些生疏。我的的建議是,在沒有特殊需求的狀況下,使用第三種和第四種方式實現單例模式。