漫畫:什么是單例模式

image
image
public class Singleton {
    private Singleton() {}  //私有構(gòu)造函數(shù)
    private static Singleton instance = null;  //單例對象
    //靜態(tài)工廠方法
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
image

我們來解釋幾個關(guān)鍵點:

  • 要想讓一個類只能構(gòu)建一個對象,自然不能讓它隨便去做new操作,因此Signleton的構(gòu)造方法是私有的。

  • instance是Singleton類的靜態(tài)成員,也是我們的單例對象。它的初始值可以寫成Null,也可以寫成new Singleton()。至于其中的區(qū)別后來會做解釋。

  • getInstance是獲取單例對象的方法。

如果單例初始值是null,還未構(gòu)建,則構(gòu)建單例對象并返回。這個寫法屬于單例模式當(dāng)中的懶漢模式,上面小新寫的就是。

如果單例對象一開始就被new Singleton()主動構(gòu)建,則不再需要判空操作,這種寫法屬于餓漢模式。代碼如下:


public class EagerSingleton(){
 
    private static final EagerSingleton instance = new EagerSingleton();
 
    private EagerSingleton(){};
 
    public static EagerSingleton getInstance(){
 
       return instance;
    }
}

image.png

為了實現(xiàn)線程安全,我們一般采用雙重檢測機(jī)制,代碼如下:

public class Singleton {
    private Singleton() {}  //私有構(gòu)造函數(shù)
   private volatile static Singleton instance = null;  //單例對象
   //靜態(tài)工廠方法
   public static Singleton getInstance() {
        if (instance == null) {      //雙重檢測機(jī)制
         synchronized (Singleton.class){  //同步鎖
           if (instance == null) {     //雙重檢測機(jī)制
             instance = new Singleton();
               }
            }
         }
        return instance;
    }
}

我們來解釋幾個關(guān)鍵點:

1.為了防止new Singleton被執(zhí)行多次,因此在new操作之前加上Synchronized 同步鎖,鎖住整個類(注意,這里不能使用對象鎖)。

2.進(jìn)入Synchronized 臨界區(qū)以后,還要再做一次判空。因為當(dāng)兩個線程同時訪問的時候,線程A構(gòu)建完對象,線程B也已經(jīng)通過了最初的判空驗證,不做第二次判空的話,線程B還是會再次構(gòu)建instance對象。


image.png
image.png

3、經(jīng)過volatile的修飾,當(dāng)線程A執(zhí)行instance = new Singleton的時候,JVM執(zhí)行順序是什么樣?始終保證是下面的順序:

memory =allocate(); //1:分配對象的內(nèi)存空間
ctorInstance(memory); //2:初始化對象
instance =memory; //3:設(shè)置instance指向剛分配的內(nèi)存地址

如此在線程B看來,instance對象的引用要么指向null,要么指向一個初始化完畢的Instance,而不會出現(xiàn)某個中間態(tài),保證了安全。

volatile的存在保證不發(fā)生指令重排序,從而使得其他線程訪問一個未必初始化完成的類。

image.png

image.png

用靜態(tài)內(nèi)部類實現(xiàn)單例模式:

public class Singleton {
    private static class LazyHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton (){}
    public static Singleton getInstance() {
        return LazyHolder.INSTANCE;
    }
}
image.png

利用反射打破單例:

//獲得構(gòu)造器
Constructor con = Singleton.class.getDeclaredConstructor();
//設(shè)置為可訪問
con.setAccessible(true);
//構(gòu)造兩個不同的對象
Singleton singleton1 = (Singleton)con.newInstance();
Singleton singleton2 = (Singleton)con.newInstance();
//驗證是否是不同對象
System.out.println(singleton1.equals(singleton2));

代碼可以簡單歸納為三個步驟:

第一步,獲得單例類的構(gòu)造器。

第二步,把構(gòu)造器設(shè)置為可訪問。

第三步,使用newInstance方法構(gòu)造對象。

最后為了確認(rèn)這兩個對象是否真的是不同的對象,我們使用equals方法進(jìn)行比較。毫無疑問,比較結(jié)果是false。

image.png
image.png

用枚舉實現(xiàn)單例模式:

public enum SingletonEnum {
    INSTANCE;
}

最后總結(jié)一下:

  1. volatile關(guān)鍵字不但可以防止指令重排,也可以保證線程訪問的變量值是主內(nèi)存中的最新值。

2.使用枚舉實現(xiàn)的單例模式,不但可以防止利用反射強(qiáng)行構(gòu)建單例對象,而且可以在枚舉類對象被反序列化的時候,保證反序列的返回結(jié)果是同一對象。對于其他方式實現(xiàn)的單例模式,如果既想要做到可序列化,又想要反序列化為同一對象,則必須實現(xiàn)readResolve方法。

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

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

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