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