日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

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

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

 前言

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

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

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多