更新時間:2020-08-24 來源:黑馬程序員 瀏覽量:
單例模式(Singleton Pattern)顧名思義就是只有一個實例,是一種常用的軟件設計模,設計模式屬于創建型模式它提供了一種創建對象的最佳方式,但是在Java中要用好單例模式,并不是一件簡單的事。在整個系統中,單例類只能有一個實例對象,且需要自行完成示例,并始終對外提供同一實例對象。
注意:
1、單例類只能有一個實例。
2、單例類必須自己創建自己的唯一實例。
3、單例類必須給所有其他對象提供這一實例。
意圖:保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。
主要解決:一個全局使用的類頻繁地創建與銷毀。
何時使用:當您想控制實例數目,節省系統資源的時候。
如何解決:判斷系統是否已經有這個單例,如果有則返回,如果沒有則創建。
關鍵代碼:構造函數是私有的。
優點:
在內存中只有一個對象,節省內存空間;
避免頻繁的創建銷毀對象,可以提高性能;
避免對共享資源的多重占用,簡化訪問;
為整個系統提供一個全局訪問點。
缺點:
不適用于變化頻繁的對象;
濫用單例將帶來一些負面問題,如為了節省資源將數據庫連接池對象設計為的單例類,可能會導致共享連接池對象的程序過多而出現連接池溢出;
如果實例化的對象長時間不被利用,系統會認為該對象是垃圾而被回收,這可能會導致對象狀態的丟失;
如果我們在代碼中需要一個全局類,我們可以讓它變成一個單例。例如,我們在系統的多個地方需要讀取一個配置文件,我們并不需要每次都去new一個實例,然后去讀文件,只需要維護一個全局的Config類,并且每次使用的時候校驗下文件是否變更即可。依賴可以減少類的創建跟銷毀的時候的開銷,二來也減少了讀取文件的次數。又如我們需要維護一個計數器,我們當然不想每次統計的時候都穿透去寫DB,我們可以先寫到內存當中。還有,在程序開發中,我們常常運用到各種池化技術,我們可以將線程池,連接池作為一個單例,統一進行分配跟管理
單例模式在多線程的應用場合下必須小心使用。如果當唯一實例尚未創建時,有兩個線程同時調用創建方法,那么它們同時沒有檢測到唯一實例的存在,從而同時各自創建了一個實例,這樣就有兩個實例被構造出來,從而違反了單例模式中實例唯一的原則。解決這個問題的辦法是為指示類是否已經實例化的變量提供一個互斥鎖(雖然這樣會降低效率)。
單例模式有很多種寫法,但是有些寫法在特定的場景下,尤其是多線程條件下,無法滿足實現單一實例對象的要求,從而導致錯誤。下面我們來介紹單例模式的五種寫法。
1、懶漢式
懶漢式,顧名思義就是實例在用到的時候才去創建,“比較懶”,用的時候才去檢查有沒有實例,如果有則返回,沒有則新建。有線程安全和線程不安全兩種寫法,區別就是synchronized關鍵字。
優點:獲取對象的速度快,線程安全(因為虛擬機保證只會裝載一次,在裝載類的時候是不會發生并發的)
缺點:耗內存(若類中有靜態方法,在調用靜態方法的時候類就會被加載,類加載的時候就完成了單例的初始化,拖慢速度)。
代碼演示:
public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
2、餓漢式
餓漢式,從名字上也很好理解,就是“比較勤”,實例在初始化的時候就已經建好了,不管你有沒有用到,都先建好了再說。
優點:單例只有在使用時才被實例化,一定程度上節約了資源
缺點:加入synchronized關鍵字,造成不必要的同步開銷。不建議使用。
代碼演示:
// 懶漢式單例 public class Singleton2 { // 指向自己實例的私有靜態引用 private static Singleton2 singleton2; // 私有的構造方法 private Singleton2(){} // 以自己實例為返回值的靜態的公有方法,靜態工廠方法 public static Singleton2 getSingleton2(){ // 被動創建,在真正需要使用時才去創建 if (singleton2 == null) { singleton2 = new Singleton2(); } return singleton2; } }
3、雙檢鎖
雙檢鎖,又叫雙重校驗鎖,綜合了懶漢式和餓漢式兩者的優缺點整合而成。看上面代碼實現中,特點是在synchronized關鍵字內外都加了一層 if 條件判斷,這樣既保證了線程安全,又比直接上鎖提高了執行效率,還節省了內存空間。
優點:線程安全;延遲加載;效率較高。
代碼演示:
public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
4、靜態內部類
靜態內部類的方式效果類似雙檢鎖,但實現更簡單。但這種方式只適用于靜態域的情況,雙檢鎖方式可在實例域需要延遲初始化時使用。
優點:避免了線程不安全,延遲加載,效率高。
代碼演示:
//阻止發生派生,而派生可能會增加實例 public sealed class Singleton{ //在第一次引用類的任何成員時創建實例,公共語言運行庫負責處理變量初始化 private static readonly Singleton instance=new Singleton(); private Singleton() { } public static Singleton GetInstance(){ return instance; } }
5、枚舉
枚舉的方式是比較少見的一種實現方式,但是看上面的代碼實現,卻更簡潔清晰。并且她還自動支持序列化機制,絕對防止多次實例化。
優點
系統內存中該類只存在一個對象,節省了系統資源,對于一些需要頻繁創建銷毀的對象,使用單例模式可以提高系統性能。
缺點
當想實例化一個單例類的時候,必須要記住使用相應的獲取對象的方法,而不是使用new,可能會給其他開發人員造成困擾,特別是看不到源碼的時候。
代碼演示:
public enum Singleton { INSTANCE; public void whateverMethod() { } }