前言 在上一篇文章中,講解了關(guān)于設(shè)計模式的一些背景和基礎(chǔ)知識。那么從這一篇開始,我們就正式開始設(shè)計模式專題的學習。 什么是單例模式顧名思義,單例單例,單個實例,使用該設(shè)計模式保證一個類僅有一個實例。 使用場景在項目中全局只需要一個對象實例就可以考慮使用單例模式,例如Spring框架中的Bean、讀取配置文件的類等等。 單例模式實現(xiàn)方式單例模式實現(xiàn)的方式很多,總結(jié)為以下5種方式: 餓漢式 懶漢式 雙重檢查鎖 靜態(tài)內(nèi)部類 枚舉 有的小伙伴可能會問為什么會有這么多的實現(xiàn)方式?當然是因為已有的實現(xiàn)方式有缺陷才會誕生新的方式。 餓漢式public class HungrySingleton { /** * 提前創(chuàng)建對象 */ private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton(); /** * 構(gòu)造函數(shù)私有化 */ private HungrySingleton() { } /** * 對外提供獲取單例對象的靜態(tài)方法 * @return HungrySingleton */ public static HungrySingleton getInstance() { return HUNGRY_SINGLETON; } }12345678910111213141516171819202122復(fù)制代碼類型:[java] 從代碼來看,之所以叫餓漢式是因為太餓了,不想等待食物生產(chǎn)時間,所以提前創(chuàng)建了對象,有一種迫不及待的心情。 優(yōu)點:實現(xiàn)簡單,沒有線程安全問題 缺點:如果該對象不使用就相當于在浪費內(nèi)存 懶漢式懶漢式較比餓漢式來說,延時加載對象,在第一次調(diào)用獲取對象的方法時才正式創(chuàng)建對象。 第一種: public class LazySingleton { /** * 定義單例對象變量 */ private static LazySingleton lazySingleton; /** * 構(gòu)造函數(shù)私有化 */ private LazySingleton() { } /** * 第一種方式 * 對外提供獲取單例對象的靜態(tài)方法 * 缺點:線程不安全 * @return LazySingleton */ public static LazySingleton getInstanceOne() { if (lazySingleton == null) { lazySingleton = new LazySingleton(); } return lazySingleton; } }123456789101112131415161718192021222324252627復(fù)制代碼類型:[java] 缺點:在多線程下是不安全的,假設(shè)A線程執(zhí)行完lazySingleton == null還沒執(zhí)行實例化語句,此時對B線程來說lazySingleton還是為null,會跟A線程同時執(zhí)行實例化。盡管對于AB線程執(zhí)行完方法后才訪問的其它線程來說最終拿到的對象是同一個,但是多實例化了一次對象,給程序帶來垃圾對象,如果初始化完成之前訪問的線程越多,那造成的垃圾對象也就越多。 第二種: public class LazySingleton { /** * 定義單例對象變量 */ private static LazySingleton lazySingleton; /** * 構(gòu)造函數(shù)私有化 */ private LazySingleton() { } /** * 第二種方式 * 通過 synchronized 保證線程安全 * 缺點:鎖方法導(dǎo)致并發(fā)量降低 * @return LazySingleton */ public static synchronized LazySingleton getInstanceTwo() { if (lazySingleton == null) { lazySingleton = new LazySingleton(); } return lazySingleton; } }123456789101112131415161718192021222324252627復(fù)制代碼類型:[java] 優(yōu)點:對比第一種,解決了線程安全問題 缺點:由于加了synchronized鎖,會降低并發(fā)量 雙重檢查鎖(DCL-> Double-Checked-Lock)雙重檢查鎖方式我們從名稱上能大致猜的出來,會有兩次判斷,那么具體怎么實現(xiàn)我們來看下面的代碼。 public class LazySingleton { /** * 定義單例對象變量 * volatile 解決 getInstance() 非原子性操作問題 */ private static volatile LazySingleton lazySingleton; /** * 構(gòu)造函數(shù)私有化 */ private LazySingleton() { } /** * 優(yōu)點:降低 synchronized 鎖的范圍 提高并發(fā)量 * 缺點:實例化對象非原子性操作 * 解決方案:使用 volatile 關(guān)鍵字修飾引用 * @return LazySingleton */ public static LazySingleton getInstance() { // 第一重檢查 if (lazySingleton == null) { synchronized (LazySingleton.class) { // 第二重檢查 if (lazySingleton == null) { // 非原子性操作 可能導(dǎo)致對象創(chuàng)建不完整 // 解決方案:給 lazySingleton 加上 volatile lazySingleton = new LazySingleton(); } } } return lazySingleton; } }123456789101112131415161718192021222324252627282930313233343536復(fù)制代碼類型:[java] 優(yōu)點:較比懶漢式的第二種降低 synchronized 鎖的范圍,提高并發(fā)量。 缺點:因為實例化對象語句在被解析成字節(jié)碼的時候是多個指令,非原子性操作,可能會出現(xiàn)對象創(chuàng)建不完整的情況,這個涉及JVM的知識,這里就不細講了。解決方案是給 lazySingleton 變量加上volatile關(guān)鍵字。 靜態(tài)內(nèi)部類靜態(tài)內(nèi)部類的方式,是通過在單例Class中再創(chuàng)建個靜態(tài)的Class,通過在靜態(tài)類中進行實例化從而達到線程安全的懶加載效果。靜態(tài)內(nèi)部類的用法也許很多老鐵們用的不多,實際工作中也不怎么解除,但是這種編碼方式在開源框架中是很常見的。 public class InnerSingleton { /** * 構(gòu)造函數(shù)私有化 */ private InnerSingleton() { } /** * 在靜態(tài)內(nèi)部類中初始化對象 */ private static class SingletonHolder { private static final InnerSingleton INNER_SINGLETON = new InnerSingleton(); } /** * 第一次調(diào)用該方法會觸發(fā)內(nèi)部類中的初始化 * JVM保證靜態(tài)類在初始化過程中只初始化一次 * @return InnerSingleton */ public static InnerSingleton getInstance() { return SingletonHolder.INNER_SINGLETON; } }1234567891011121314151617181920212223242526復(fù)制代碼類型:[java] 優(yōu)點:線程安全的懶加載方式,同時不會降低并發(fā)量 枚舉上面已經(jīng)講了好幾種實現(xiàn)單例的方式以及優(yōu)缺點,并且靜態(tài)內(nèi)部類看起來也近乎完美,為什么還需要講枚舉呢。其實可以使用反射和序列化的方式打破上述方案的安全性,下面我們就來看下具體如何實現(xiàn)。 import java.io.*;import java.lang.reflect.Constructor;/** * 需實現(xiàn) Serializable 接口 */public class InnerSingleton implements Serializable { /** * 構(gòu)造函數(shù)私有化 */ private InnerSingleton() { } /** * 在靜態(tài)內(nèi)部類中初始化對象 */ private static class SingletonHolder { private static final InnerSingleton INNER_SINGLETON = new InnerSingleton(); } /** * 第一次調(diào)用該方法會觸發(fā)內(nèi)部類中的初始化 * JVM保證靜態(tài)類在初始化過程中只初始化一次 * * @return InnerSingleton */ public static InnerSingleton getInstance() { return SingletonHolder.INNER_SINGLETON; } /** * 序列化解決方案 * @return Object */// private Object readResolve() {// return SingletonHolder.INNER_SINGLETON;// } public static void main(String[] args) throws Exception { InnerSingleton innerSingleton = InnerSingleton.getInstance(); // 反射方式 Constructor<InnerSingleton> constructor = InnerSingleton.class.getDeclaredConstructor(); constructor.setAccessible(true); InnerSingleton newInstance = constructor.newInstance(); // 判斷是否是同一個對象 System.out.println(innerSingleton == newInstance); // 序列化方式 // 把對象寫到文件中 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("single.txt")); oos.writeObject(innerSingleton); oos.close(); // 從原文件中讀取對象 File file = new File("single.txt"); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); InnerSingleton ioNewInstance = (InnerSingleton) ois.readObject(); ois.close(); // 判斷是否是同一個對象 System.out.println(innerSingleton == ioNewInstance); } }123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566復(fù)制代碼類型:[java] 運行結(jié)果: falsefalse12復(fù)制代碼類型:[java] 序列化的解決方案將 readResolve 方法注釋打開即可,原理在本篇中不做講解,后面有機會可以單獨寫一篇。最后我們來實現(xiàn)下枚舉,如下所示。 import java.lang.reflect.Constructor;public enum EnumSingleton { /** * EnumSingleton的一個實例,天然單例 */ INSTANCE; public static void main(String[] args) throws Exception { EnumSingleton instance1 = EnumSingleton.INSTANCE; EnumSingleton instance2 = EnumSingleton.INSTANCE; System.out.println(instance1 == instance2); // 反射實例化對象 // 獲取自身構(gòu)造器 Constructor<EnumSingleton> constructor1 = EnumSingleton.class.getDeclaredConstructor(); constructor1.setAccessible(true); EnumSingleton instance3 = constructor1.newInstance(); // 獲取父類構(gòu)造器 Constructor<EnumSingleton> constructor2 = EnumSingleton.class.getDeclaredConstructor(String.class, int.class); constructor2.setAccessible(true); EnumSingleton instance4 = constructor2.newInstance(); System.out.println(instance1 == instance3); System.out.println(instance2 == instance3); System.out.println(instance3 == instance4); } }123456789101112131415161718192021222324252627復(fù)制代碼類型:[java] 運行結(jié)果: true// 自身構(gòu)造器運行結(jié)果Exception in thread "main" java.lang.NoSuchMethodException: com.javafamily.EnumSingleton.<init>() at java.base/java.lang.Class.getConstructor0(Class.java:3349) at java.base/java.lang.Class.getDeclaredConstructor(Class.java:2553) at com.javafamily.EnumSingleton.main(EnumSingleton.java:17)// 父類構(gòu)造器運行結(jié)果Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:484) at com.javafamily.EnumSingleton.main(EnumSingleton.java:24)12345678910復(fù)制代碼類型:[java] 優(yōu)點:實現(xiàn)簡單,線程安全,自動支持序列化機制,通過反射生成新的實例會拋出異常,第一個異常通過getDeclaredConstructors()獲取所有構(gòu)造方法,會發(fā)現(xiàn)并沒有無參構(gòu)造方法,只有參數(shù)為(String.class,int.class)構(gòu)造方法,而該方法恰好是Enum類。而第二個異常在 newInstance 方法中已經(jīng)說明原因,如果該類被Enum修飾,則無法發(fā)射拋出異常。 @CallerSensitive@ForceInline // to ensure Reflection.getCallerClass optimizationpublic T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{ if (!override) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, clazz, modifiers); } if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects"); ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; }1234567891011121314151617181920復(fù)制代碼類型:[java] 總結(jié):至此,單例的實現(xiàn)方式都講完了,也許在實際開發(fā)中除了枚舉以外,其它實現(xiàn)單例的方式都比較常見,但枚舉是《Effective Java》 作者提倡的方式,相信該方式在未來也將慢慢成為主流。 |
|
來自: 碼農(nóng)9527 > 《Java》