小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

創(chuàng)建型-單例模式(Singleton Pattern)

 碼農(nóng)9527 2021-11-25

 前言

  在上一篇文章中,講解了關(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》 作者提倡的方式,相信該方式在未來也將慢慢成為主流。

    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多