轉載請注明原文地址:https://www.cnblogs.com/ygj0930/p/10845530.html
一:靜態內部類實現單例模式
原理:通過一個靜態內部類定義一個靜態變量來持有當前類實例,在類加載時就創建好,在使用時獲取。
缺點:無法做到延遲創建對象,在類加載時進行創建會導致初始化時間變長。
public class SingletonInner { private static class Holder { private static SingletonInner singleton = new SingletonInner(); } private SingletonInner(){} public static SingletonInner getSingleton(){ return Holder.singleton; }
二:餓漢模式
原理:創建好一個靜態變量,每次要用時直接返回。
缺點:無法做到延遲創建對象,在類加載時進行創建會導致初始化時間變長。
public class SingletonHungry { private static SingletonHungry instance = new SingletonHungry(); private SingletonHungry() { } public static SingletonHungry getInstance() { return instance; }
三:懶漢模式
原理:延遲創建,在第一次用時才創建,之后每次用到就返回創建好的。
缺點:由於synchronized的存在,多線程時效率很低。
public class SingletonLazy { private static volatile SingletonLazy instance; private SingletonLazy() { } public static synchronized SingletonLazy getInstance() { if (instance == null) { instance = new SingletonLazy(); } return instance; }
四:雙重校驗鎖懶漢模式
原理:在getSingleton()方法中,進行兩次null檢查。這樣可以極大提升並發度,進而提升性能。
public class Singleton { private static volatile Singleton singleton = null;//使用valatile,使該變量能被所有線程可見 private Singleton(){} public static Singleton getSingleton(){ if(singleton == null){ synchronized (Singleton.class){//初始化時,加鎖創建 if(singleton == null){//為避免在加鎖過程中被其他線程創建了,再作一次非空校驗 singleton = new Singleton(); } } } return singleton; }
五:上述4種方式實現單例模式的缺點
1、反序列化對象時會破環單例
反序列化對象時不會調用getXX()方法,於是繞過了確保單例的邏輯,直接生成了一個新的對象,破環了單例。
解決辦法是:重寫類的反序列化方法,在反序列化方法中返回單例而不是創建一個新的對象。
public class Singleton implements java.io.Serializable { public static Singleton INSTANCE = new Singleton(); protected Singleton() { } //反序列時直接返回當前INSTANCE private Object readResolve() { return INSTANCE; } }
2、在代碼中通過反射機制,直接調用類的私有構造函數創建新的對象,會破環單例
解決辦法是:維護一個volatile的標志變量在第一次創建實例時置為false;重寫構造函數,根據標志變量決定是否允許創建。
private static volatile boolean flag = true; private Singleton(){ if(flag){ flag = false; //第一次創建時,改變標志 }else{ throw new RuntimeException("The instance already exists !"); }
六:枚舉模式實現單例——將單例維護在枚舉類中作為唯一實例
原理:定義枚舉類型,里面只維護一個實例,以此保證單例。每次取用時都從枚舉中取,而不會取到其他實例。
public enum SingletonEnum { INSTANCE; private String name; public String getName(){ return name; } public void setName(String name){ this.name = name; }
優點:
1)使用SingletonEnum.INSTANCE
進行訪問,無需再定義getInstance方法和調用該方法。
2)JVM對枚舉實例的唯一性,避免了上面提到的反序列化和反射機制破環單例的情況出現:每一個枚舉類型和定義的枚舉變量在JVM中都是唯一的。
原因:枚舉類型在序列化時僅僅是將枚舉對象的name屬性輸出到結果中,反序列化的時候則是通過java.lang.Enum的valueOf方法來根據名字查找枚舉對象。
同時,編譯器禁止重寫枚舉類型的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。