摘要:單例模式是一種常用的設計模式也可能是設計模式中代碼量最少的設計模式。簡介單例模式屬于中設計模式中的創建型模式定義是確保某一個類只有一個實例并提供一個全局的訪問點。
單例模式是一種常用的設計模式、也可能是設計模式中代碼量最少的設計模式。但是少并不意味著簡單、想要用好、用對單例、就的費一番腦子了。因為它里面涉及到了很多Java底層的知識如類裝載機制、Java內存模型、volatile等知識點。
簡介單例模式屬于23中設計模式中的創建型模式、定義是確保某一個類只有一個實例、并提供一個全局的訪問點。
具有以下3個特性:
只能有一個實例
必須自己創建自己唯一實例
提供全局訪問點
基本實現思路單例要求類只能返回同一對象的引用、必須提供一個靜態獲取該實例的方法
實現可以通過以下兩步:
私有化構造方法、防止外部實例化、只有通過對外提供的靜態方法來獲取唯一實例
提供一個靜態方法獲取對象的實例。
單例的7種實現方式public class EagetSingleton { private static final EagetSingleton INSANCE = new EagetSingleton(); // 私有化構造函數、防止外部實例化 private EagetSingleton() { } // 提供靜態外部訪問方法 public static EagetSingleton getInstance() { return INSANCE; } }
優點:寫法簡單、類裝載時就實例化了靜態變量、避免了線程并發問題。
缺點:在類裝載過程中就實例化了對象、造成了資源浪費。
public class StaticBlockSingleton { private static StaticBlockSingleton INSTANCE = null; static { try { INSTANCE = new StaticBlockSingleton(); } catch (Exception e) { } } // 私有化構造函數、防止外部實例化 private StaticBlockSingleton() { } // 提供靜態外部訪問方法 public static StaticBlockSingleton getInstance() { return INSTANCE; } }
這種方式和上述實現方式基本相同、只是把類實例化的過程放到了靜態代碼塊中來實例化、同樣也是在類裝載過程執行靜態代碼塊、優缺點基本相同但是它可以在類實例化過程中做一些額外的操作如異常處理等。
public class LazySingleton { private static LazySingleton INSTANCE = null; // 私有化構造函數、防止外部實例化 private LazySingleton() { } // 提供靜態外部訪問方法 public static LazySingleton getInstance() { if (null == INSTANCE) { -------- 1 INSTANCE = new LazySingleton(); ------2 } return INSTANCE; } }
優點:實現了懶加載、避免了資源的浪費。
缺點:線程不安全、在多線程情況下當一個線程執行到 1 處的時候、還沒有來得及往下執行另一個線程也到 1 處 這樣兩個線程同時執行 2 處代碼、破壞了單例。
public class LazySyncSingleton { private static LazySyncSingleton INSTANCE = null; // 私有化構造函數、防止外部實例化 private LazySyncSingleton() { } // 效率低下 // 提供靜態外部訪問方法 public static synchronized LazySyncSingleton getInstance() { if (null == INSTANCE) { INSTANCE = new LazySyncSingleton(); } return INSTANCE; } }
解決了3中線程不安全的問題、利用synchronized對getInstance()方法加鎖以達到同步訪問。
優點:線程同步
缺點:效率低下、此方式對整個對象加鎖、每次訪問getInstance() 都需要同步訪問、這種情況多線程并發效率非常低下、其實我們只需要在對象還沒實例化前加鎖就可以了、實例化后就不存在并發問題了。
public class DCheckSingleton { private static volatile DCheckSingleton INSTANCE = null; // 私有化構造函數、防止外部實例化 private DCheckSingleton() { } // 提供靜態外部訪問方法 public static DCheckSingleton getInstance() { if (null == INSTANCE) { synchronized (DCheckSingleton.class) { if (null == INSTANCE) { INSTANCE = new DCheckSingleton(); } } } return INSTANCE; } }
解決了4中并發情況下效率低下的問題。
優點:線程安全、延遲加載、效率高
涉及到知識點: 1:volatile 關鍵字 確保內存的可見性和有序性。如果不加volatile關鍵字會有什么情況? 我知道在對象實例化時INSTANCE = new DCheckSingleton();這一句代碼JVM中并不是一步執行的而是分為三步(1)在棧內存中為 創建對象的引用指針 INSTANCE (2)在堆內存中開辟一塊空間來存放實例化的對象 new DCheckSingleton(); (3)將INSTANCE指向堆內存空間地址J、VM只保證了代碼執行結果的正確性、并不保證執行順序(這里涉及到Java內存模型知識點在這就不多說了、感興趣的同學可以去了解下JVM一些底層實現原理)所以 1 ,2,3三步也可能是1 ,3 ,2 這樣我們就可能拿到的時一個半成品的對象了。
2: 涉及到類實例化知識點
3: 涉及到Java內存模型
4:涉及到JVM的一些執行優化、指令重排等
public class InnerSingleton { private InnerSingleton() { } public static InnerSingleton getInstance() { return InnerClassSingleton.INSTANCE; } private static class InnerClassSingleton{ private static final InnerSingleton INSTANCE = new InnerSingleton(); } }
這種方式和餓漢式的實現機制基本相同、都是利用了類裝載機制來保證線程的安全、它和餓漢式的唯一區別就是實現了懶加載的機制、只有在調用getInstance()方法時才去進行InnerClassSingleton類的實例化。
優點:避免了線程不安全,延遲加載,效率高。
public enum EnumsSingleton { INSTANCE; @SuppressWarnings("unused") private void method() { System.out.println("------- newInstance"); } }
借助JDK1.5中添加的枚舉來實現單例模式。不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象。可能是因為枚舉在JDK1.5中才添加、所以在實際項目開發中、很少見人這么寫過。
到這單例幾種實現方式以及每種方式的優缺點都做了一些簡單的介紹、枚舉雖小但是設計的知識點很多。
優點在單例模式中,活動的單例只有一個實例,對單例類的所有實例化得到的都是相同的一個實例。這樣就 防止其它對象對自己的實例化,確保所有的對象都訪問一個實例
單例模式具有一定的伸縮性,類自己來控制實例化進程,類就在改變實例化進程上有相應的伸縮性。
提供了對唯一實例的受控訪問。
由于在系統內存中只存在一個對象,因此可以 節約系統資源,當 需要頻繁創建和銷毀的對象時單例模式無疑可以提高系統的性能。
允許可變數目的實例。
避免對共享資源的多重占用
缺點不適用于變化的對象,如果同一類型的對象總是要在不同的用例場景發 生變化,單例就會引起數據的錯誤,不能保存彼此的狀態。
由于單利模式中沒有抽象層,因此單例類的擴展有很大的困難。
單例類的職責過重,在一定程度上違背了“單一職責原則”。
濫用單例將帶來一些負面問題,如為了節省資源將數據庫連接池對象設 計為的單例類,可能會導致共享連接池對象的程序過多而出現連接池溢 出;如果實例化的對象長時間不被利用,系統會認為是垃圾而被回收, 這將導致對象狀態的丟失。
使用場景需要頻繁的進行創建和銷毀的對象;
創建對象時耗時過多或耗費資源過多,但又經常用到的對象;
工具類對象;
頻繁訪問數據庫或文件的對象。
注意最后在簡單聊一下如何防止暴力破壞單例。主要介紹兩種方式以及如何來防范這兩種方式。
1: 利用Java的反射方式
EagerSingleton instance = EagerSingleton.getInstance(); Constructor instance2 = instance.getClass().getDeclaredConstructor(); instance2.setAccessible(true); EagerSingleton instance3 = (EagerSingleton) instance2.newInstance(); System.out.println("===" + instance); System.out.println("===" + instance3);
利用Java的反射方式可以達到爆力破解單例的效果、運行結果我就不在這貼出了有興趣的可以自己試試instance 和 instance3 肯定不是一個對象。
如何來防范這方式? 其實也很簡單Java Security 中為我們提供了現成的方法。只需要在私有構造中使用SecurityManager 進行檢查下就可以代碼如下。
// 私有的構造方法,防止外部實例化 private EagerSingleton() { SecurityManager sm = new SecurityManager(); sm.checkPermission(new ReflectPermission("禁止反射")); }
2: 第二種方式是利用Java 序列化和反序列化來實現
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("1.txt")); out.writeObject(instance); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("1.txt")); EagerSingleton readObject = (EagerSingleton) in.readObject(); in.close(); System.out.println("==" + instance); System.out.println("==" + readObject);
如何防范? 很簡單只需要重寫readResolve() 反方就可以了
private Object readResolve() { return EagerSingleton.instance; }
兩種暴力破解和防范的方式都介紹完了,感興趣的同志可以去試試我這里沒有貼出完整的測試代碼和運行結果。
~~~~~~到這我們的小單例已經介紹完了,有沒有感到驚訝!!!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/74244.html
摘要:老實說,當時一進入世界的大門就暈了,各種規范概念和英文縮寫詞能把人整的暈暈乎乎。等新的英文縮寫又出現了,一口老血還沒來得及噴出,又重新振作開始新的學習征程。 showImg(http://upload-images.jianshu.io/upload_images/1131767-1c5d16e39435df10.jpg?imageMogr2/auto-orient/strip%7Ci...
摘要:大家好,我是冰河有句話叫做投資啥都不如投資自己的回報率高。馬上就十一國慶假期了,給小伙伴們分享下,從小白程序員到大廠高級技術專家我看過哪些技術類書籍。 大家好,我是...
摘要:前言單例模式是設計模式中最簡單最容易理解的一種,維基百科的定義如下單例模式,也叫單子模式,是一種常用的軟件設計模式。 前言 單例模式是設計模式(Design Pattern)中最簡單、最容易理解的一種,維基百科[1]的定義如下: 單例模式,也叫單子模式,是一種常用的軟件設計模式。在應用這個模式時,單例對象的類 類 (計算機科學))必須保證只有一個實例存在。許多時候整個系統只需要擁有一...
閱讀 3228·2021-11-15 11:37
閱讀 2449·2021-09-29 09:48
閱讀 3813·2021-09-22 15:55
閱讀 3014·2021-09-22 10:02
閱讀 2636·2021-08-25 09:40
閱讀 3225·2021-08-03 14:03
閱讀 1691·2019-08-29 13:11
閱讀 1570·2019-08-29 12:49