05 - 單例模式

定義: 某一種對(duì)象,全局只能創(chuàng)建一個(gè)。

單例模式猶如茴香豆有N中寫法。

  1. 構(gòu)造函數(shù)設(shè)置成私有
  2. 靜態(tài)構(gòu)造方法構(gòu)造對(duì)象。
public class Singleton {
  private static final Singleton s = new Singleton();
  private Singleton() {
  }
  public static Singleton getInstance() {
        return s;
   }
}

按照java的語法,都知道靜態(tài)變量會(huì)自動(dòng)初始化,并且final的變量只會(huì)初始化一次并且引用不可改變,這樣我們只有一個(gè)Singleton對(duì)象的引用。

但是這個(gè)Singleton對(duì)象什么時(shí)候創(chuàng)建出來的呢,什么時(shí)候執(zhí)行的呢?

反編譯一下:

Classfile /root/java/Singleton.class
  Last modified Jun 4, 2018; size 369 bytes
  MD5 checksum 1820528d8fe309e7c6bff10a17dd9113
  Compiled from "Singleton.java"
public class Singleton
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#17         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#18         // Singleton.s:LSingleton;
   #3 = Class              #19            // Singleton
   #4 = Methodref          #3.#17         // Singleton."<init>":()V
   #5 = Class              #20            // java/lang/Object
   #6 = Utf8               s
   #7 = Utf8               LSingleton;
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               getInstance
  #13 = Utf8               ()LSingleton;
  #14 = Utf8               <clinit>
  #15 = Utf8               SourceFile
  #16 = Utf8               Singleton.java
  #17 = NameAndType        #8:#9          // "<init>":()V
  #18 = NameAndType        #6:#7          // s:LSingleton;
  #19 = Utf8               Singleton
  #20 = Utf8               java/lang/Object
{
  public static Singleton getInstance();
    descriptor: ()LSingleton;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: getstatic     #2                  // Field s:LSingleton;
         3: areturn
      LineNumberTable:
        line 6: 0

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: new           #3                  // class Singleton
         3: dup
         4: invokespecial #4                  // Method "<init>":()V
         7: putstatic     #2                  // Field s:LSingleton;
        10: return
      LineNumberTable:
        line 2: 0
}
SourceFile: "Singleton.java"

可以看到,s的初始化放到了static()方法里,也就是類構(gòu)造器(cinit);

public class Singleton {
  private static final Singleton s 
 static { 
    s = new Singleton();
  }
  private Singleton() {
  }
  public static Singleton getInstance() {
        return s;
   }
}

這個(gè)方法什么時(shí)候執(zhí)行呢?
類初始化的時(shí)候

類的生命周期是:
加載,驗(yàn)證,準(zhǔn)備,解析,初始化,使用,卸載。

當(dāng)使用的時(shí)候,一般都是 Singleton a = Singleton.getInstance(); 此時(shí)遇到了指令invokestatic ,類必須要初始化,Singleton類 進(jìn)行加載,驗(yàn)證,準(zhǔn)備(清零),解析(接口,字段解析),初始化。

jvm在執(zhí)行cinit方法時(shí),會(huì)采取加鎖和同步的方式。避免其他線程也執(zhí)行,并且在本線程執(zhí)行完畢后,其他線程也無法執(zhí)行。

因此該對(duì)象的創(chuàng)建是線程安全的。

大多數(shù)情況下,這樣設(shè)計(jì)就可以的。


進(jìn)一步要考慮的是懶加載(調(diào)用的時(shí)候再創(chuàng)建,不要再加載的時(shí)候創(chuàng)建),線程安全,以及序列化安全。

如果創(chuàng)建該對(duì)象的時(shí)間很長,可以采用一些辦法,在使用的時(shí)候再創(chuàng)建。
內(nèi)部類懶加載

public class Single {
  private Single() {
 }
  private static class Holder {
      private final static Single s = new Single();
  }
  public static Single getInstance() {
     return Holder.s;
  }
}

這樣Single類如果因?yàn)槠渌闆r,發(fā)生了加載,也不會(huì)初始化s。
只有調(diào)用getInstance() 時(shí),加載Single類,執(zhí)行Holder.s ,對(duì)應(yīng)指令是invokestatic,加載Holder類,初始化s,并返回。這個(gè)過程是jvm控制加鎖,線程安全。


序列化安全使用枚舉即可,避免反射和序列化的攻擊。
不過jdk打算把序列化從jdk中移除出去,JDK認(rèn)為有至少三分之一的安全問題都是Serializable引起的。

END

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容