單例模式是最簡單的也是設計模式系列書籍開篇第一個講到的模式,在平時的開發中也經常用它來保證獲取的都是同一個實例。
定義:確保某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例。
餓漢模式
public class HungrySingleton { private static final HungrySingleton singleton = new HungrySingleton(); //限制外部產生HungrySingleton對象 private HungrySingleton(){ } //向外提供獲取示例的靜態方法 public static HungrySingleton getInstance() { return singleton; } //other methods }
餓漢模式是類加載時候就創建對象,利用了jvm特性保證了線程的安全性。
- getInstance()方法是static的保證了通過類名可直接獲取實例
- 私有構造方法保證了只有自己才可以創建實例
懶漢模式
雙重檢查加鎖 方式
1 public class LazySingleton { 2 private static volatile LazySingleton singleton = null; 3 4 private LazySingleton() { } 5 6 public static LazySingleton getSingleton() { 7 if (singleton == null) { //不用每次獲取對象都強制加鎖,為了提升了效率 8 synchronized (LazySingleton.class) { 9 if (singleton == null) { 10 singleton = new LazySingleton(); 11 } 12 } 13 } 14 return singleton; 15 } 16 }
- 第7行代碼判空是為了提高效率,不用每次獲取對象都強制加鎖;
- 第8行同步加鎖是為了線程安全;
- 第9行判空是為了保證單例對象的唯一性,只有沒被創建才去創建。
- volatile關鍵字為了保證singleton對象的可見性並禁止編譯器對其進行編譯指令重排序優化。
靜態內部類 模式
內部類有用static修飾和不用static修飾的內部類:
- 用static修飾的是靜態內部類,相當於其外部類的static成員,不依賴與外部類的實例,靜態內部類只可以引用外部類的靜態成員屬性和靜態方法,只在第一次使用到靜態內部類的時候才會被裝載;
- 不用static修飾的內部類稱為對象級別內部類,依賴於外部的類實例
public class Singleton2 { /** * 類級的內部類,也就是靜態的成員式內部類,該內部類的實例與外部類的實例 * 沒有綁定關系,而且只有被調用到時才會裝載,從而實現了延遲加載。 */ private static class Singleton2Holder { /** * 靜態初始化器,由JVM來保證線程安全 */ private static Singleton2 singleton = new Singleton2(); } private Singleton2() { //System.out.println("singleton2 private construct method called"); } public static Singleton2 getSingleton() { //System.out.println("singleton2 getSingleton method called"); return Singleton2Holder.singleton; } private String name; public void desc() { //System.out.println("singleton2 desc method called"); } }
是餓漢模式的變種形式,利用jvm加載保證線程安全,並且實現了懶加載,只在獲取實例的時候才去創建。
JDK中單例的應用
Runtime單例實現
使用的是餓漢模式

破壞單例模式
反射破壞單例模式
/**餓漢模式——反射創建對象*/ Class<HungrySingleton> singletonClass = HungrySingleton.class; Constructor<HungrySingleton> singletonConstructor = singletonClass.getDeclaredConstructor(); singletonConstructor.setAccessible(true); /**先反射創建*/ HungrySingleton hungrySingleton = singletonConstructor.newInstance(); /**再通過單例模式獲取實例*/ HungrySingleton instance = HungrySingleton.getInstance(); System.out.println(hungrySingleton); System.out.println(instance);
通過反射修改構造函數可以被訪問,通過反射構造的結果和單例模式獲取的不是同一個對象。
序列化破壞單例模式
HungrySingleton instance = HungrySingleton.getInstance(); jsonString = JSON.toJSONString(instance); HungrySingleton singleton = JSON.parseObject(jsonString, HungrySingleton.class); System.out.println(instance == singleton);
防止被破壞
防止反射創建對象
反射通過調用構造函數來創建對象,如果在構造函數里拋出異常,就可以組織反射創建對象(這種方式不適用與懶漢模式)。
public class HungrySingleton { private static final HungrySingleton singleton = new HungrySingleton(); //限制外部產生Singleton對象 private HungrySingleton() { if (singleton != null) { throw new RuntimeException("不允許創建對象!"); } System.out.println("singleton private construct method called"); } //向外提供獲取示例的靜態方法 public static HungrySingleton getInstance() { System.out.println("create singleton instance"); return singleton; } }
再用反射創建對象會報錯
/**餓漢模式——反射創建對象*/ Class<HungrySingleton> singletonClass = HungrySingleton.class; Constructor<HungrySingleton> singletonConstructor = singletonClass.getDeclaredConstructor(); singletonConstructor.setAccessible(true); /**先反射創建*/ HungrySingleton hungrySingleton = singletonConstructor.newInstance(); /**再通過單例模式獲取實例*/ HungrySingleton instance = HungrySingleton.getInstance(); System.out.println(hungrySingleton); System.out.println(instance);
懶漢模式仍然會被破壞(當反射先於懶漢模式創建對象時,仍然會創建多個對象)
/**懶漢模式——反射創建對象*/ Class<LazySingleton> lazySingletonClass = LazySingleton.class; Constructor<LazySingleton> lazySingletonConstructor = lazySingletonClass.getDeclaredConstructor(); lazySingletonConstructor.setAccessible(true); /**先通過反射獲取實例*/ LazySingleton lazySingleton = lazySingletonConstructor.newInstance(); /**再通過單例模式獲取實例*/ LazySingleton lazyInstance = LazySingleton.getInstance(); System.out.println(lazySingleton); System.out.println(lazyInstance);
懶漢模式仍然會創建兩個對象:
singleton.LazySingleton@593634ad
singleton.LazySingleton@20fa23c1
防止反序列化創建對象
如果是使用ObjectInputStream方式序列化,可以使用readResolve方法來控制。但序列化的方法有很多種,這種方式並不可靠。
枚舉單例
《effective java》第77條:對於實例控制,枚舉類型優於readResolve。
以下是一個枚舉單例的示例
public enum EnumSingleton { INSTANCE; public String getDesc() { return "desc"; } public static void process() { System.out.println("static process method"); } private String name; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
使用時候直接獲取 EnumSingleton.INSTANCE 即可獲取當前實例,而且序列化與反序列化不會創建對象。
/**枚舉獲取實例*/ EnumSingleton instance = EnumSingleton.INSTANCE; instance.setName("this is a enum singleton"); instance.setAge(28); System.out.println("instance.name:"+instance.getName()+", instance.age:"+instance.getAge()); /**序列化*/ String jsonString = JSON.toJSONString(instance); /**反序列化創建對象*/ EnumSingleton serializerInstance = JSON.parseObject(jsonString, EnumSingleton.class); System.out.println(instance == serializerInstance); Class<EnumSingleton> enumSingletonClass = EnumSingleton.class; Constructor<EnumSingleton> enumSingletonConstructor = enumSingletonClass.getDeclaredConstructor(); enumSingletonConstructor.setAccessible(true); /**反射創建*/ EnumSingleton enumSingleton = enumSingletonConstructor.newInstance(); System.out.println(enumSingleton);
輸出:
instance.name:this is a enum singleton, instance.age:28
true
java.lang.NoSuchMethodException: singleton.EnumSingleton.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
總結,實現單例模式的唯一推薦方法,使用枚舉類來實現。
guava中的單例
guava里有個Suppliers提供了memoize方法,用法如下:
Suppliers.memoize(new Supplier<Object>() { @Override public Object get() { return new Demo(); } });
查看其實現源碼,將傳入的Suppliers作為代理傳給MemoizingSupperlizer,返回一個類型為MemoizingSupperlizer類型的Supperlier對象;如果不是MemoizingSupperlizer類型,創建一個MemoizingSupperlizer實例返回:

MemoizingSupperlizer內部get方法使用double-check方式實現了只執行一次創建對象方法

同樣的還有 ExpiringMemoizingSupplier 方法,支持過期時間只有再次調用get方法創建對象(可以用來實現緩存)
參考
《設計模式之禪》
https://www.cnblogs.com/shangxinfeng/p/6754345.html
https://www.cnblogs.com/ttylinux/p/6498822.html?utm_source=itdadao&utm_medium=referral
https://blog.csdn.net/hintcnuie/article/details/17968261
https://www.cnblogs.com/ldq2016/p/6627542.html
