單例模式是最簡單的也是設計模式系列書籍開篇第一個講到的模式,在平時的開發中也常常用它來保證獲取的都是同一個實例。html
定義:確保某一個類只有一個實例,並且自行實例化並向整個系統提供這個實例。java
public class HungrySingleton { private static final HungrySingleton singleton = new HungrySingleton(); //限制外部產生HungrySingleton對象 private HungrySingleton(){ } //向外提供獲取示例的靜態方法 public static HungrySingleton getInstance() { return singleton; } //other methods }
餓漢模式是類加載時候就建立對象,利用了jvm特性保證了線程的安全性。linux
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 }
內部類有用static修飾和不用static修飾的內部類:git
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加載保證線程安全,而且實現了懶加載,只在獲取實例的時候纔去建立。github
使用的是餓漢模式json
/**餓漢模式——反射建立對象*/ 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);
懶漢模式仍然會被破壞(當反射先於懶漢模式建立對象時,仍然會建立多個對象)jvm
/**懶漢模式——反射建立對象*/ 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裏有個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.htmlhttps://www.cnblogs.com/ttylinux/p/6498822.html?utm_source=itdadao&utm_medium=referralhttps://blog.csdn.net/hintcnuie/article/details/17968261https://www.cnblogs.com/ldq2016/p/6627542.html