第一次寫博客,也是第一篇,從單例模式開始,不足之處,望各位看官海涵。
簡介
首先我們都知道單例模式是java常用的23種設計模式之一,它的用途可謂是非常廣泛。它的核心就在於單實例,即整個環境中該類有且只能有一個對象。而java創建實例的方式已知的有四種,分別是通過new、clone、反射或者序列化這四種方式去創建實例,怎樣保證單例呢,下面且聽我一一道來。
單例模式的常見寫法:
1.基礎餓漢式單例
優點:
類加載時就去初始化,沒有線程安全問題,不能通過new創建實例
缺點:
①.能通過反射或者序列化去創建新的實例(解決方式在最后)
②.類加載時就創建好對象,可能會創建出來用不到的實例對象,這樣對內存是種浪費
/** * 基礎餓漢式單例 */ public class HungrySingleton { private static final HungrySingleton hungrySingleton = new HungrySingleton(); private HungrySingleton(){} public static HungrySingleton getInstance(){ return hungrySingleton; } }
2.簡單懶漢式
優點:
懶漢式和餓漢式不同,它不需要在類加載的時候去創建對象,而是在類實例化的時候才會去創建對象,所以不存在內存空間浪費
缺點:
①.懶漢式是線程不安全的,多個線程同時去實例化該對象有可能會生成多個實例對象,破壞單例
②.能通過反射或者序列化去創建新的實例(解決方式在最后)
/** * 普通的懶漢式 */ public class LazySingleton { private static LazySingleton lazySingleton; private LazySingleton(){} public static LazySingleton getInstance(){ if(lazySingleton == null){ lazySingleton = new LazySingleton(); } return lazySingleton; } }
3.懶漢式二(雙重校驗鎖-double check)
優點:
解決了簡單懶漢式的線程安全問題
缺點:
①.加入synchronized關鍵字,一定程度上影響性能
②.能通過反射或者序列化去創建新的實例(解決方式在最后)
/** * 雙重校驗鎖 */ public class DoubleCheckSingleton { private DoubleCheckSingleton doubleCheckSingleton; private DoubleCheckSingleton(){} public DoubleCheckSingleton getInstance(){ if(doubleCheckSingleton == null){ synchronized (DoubleCheckSingleton.class){ if(doubleCheckSingleton == null){ doubleCheckSingleton = new DoubleCheckSingleton(); } } } return doubleCheckSingleton; } }
4.懶漢式三(靜態內部類)
優點:
沒有synchronized關鍵字,不會影響性能,也沒有線程安全問題
缺點:
能通過反射或者序列化去創建新的實例(解決方式在最后)
/** * 靜態內部類 */ public class StaticInnerClassSingleton implements Serializable { private StaticInnerClassSingleton(){} public static StaticInnerClassSingleton getInstance(){ return InnerClass.staticInnerClassSingleton; } static class InnerClass{ private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton(); } }
5.枚舉類型單例
優點:枚舉的優點就是沒有缺點(我本人沒有發現,各位大佬有知道的可以告訴我)
/** * 枚舉類型單例 */ public enum EnumSingleton { INSTANCE; public static EnumSingleton getInstance(){ return INSTANCE; } }
反射和序列化破壞單例的解決辦法
反射和序列化是破壞單例的兩種常見方式,我們在靜態內部類的基礎上添加解決方案。
首先我們為該類實現Serializable接口,重寫readResolve方法,這樣能夠解決序列化破壞單例的問題;其次,我們在原本空的構造方法中添加一段代碼,去判斷實例是否已經存在,如果存在則拋異常,這樣能夠解決反射破壞單例的問題
import java.io.Serializable; /** * 靜態內部類 */ public class StaticInnerClassSingleton implements Serializable { private StaticInnerClassSingleton(){ if(InnerClass.staticInnerClassSingleton != null){ throw new RuntimeException("不允許創建多個實例"); } } public static StaticInnerClassSingleton getInstance(){ return InnerClass.staticInnerClassSingleton; } static class InnerClass{ private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton(); } //保證序列化不回破壞單例 private Object readResolve(){ return InnerClass.staticInnerClassSingleton; } }
總結
簡單懶漢式是我們極不推薦的一種形式,因為會存在線程安全的問題,雙重校驗鎖和基礎餓漢式是通過犧牲一定性能或者空間來達到實現單例的目的,如果性能要求不高或者內存空間足夠的話,可以酌情使用。我們更加推薦的形式是靜態內部類和枚舉類型的單例,尤其是枚舉類型,不需要通過額外的代碼去防止序列化和反射破壞單例,這是jdk開發者在源碼層面就做過限制的,相對而言,靜態內部類雖然需要自己手動去做校驗,但是它簡單易懂,很容易讓人理解。