設(shè)計(jì)模式之單例模式

一、定義

單例(Singleton)模式的定義:指一個(gè)類只有一個(gè)實(shí)例,且該類能自行創(chuàng)建這個(gè)實(shí)例的一種模式。

二、實(shí)現(xiàn)原理

使用一個(gè)私有構(gòu)造函數(shù)、一個(gè)私有靜態(tài)變量以及一個(gè)公有靜態(tài)函數(shù)來實(shí)現(xiàn)。

私有構(gòu)造函數(shù)保證了不能通過構(gòu)造函數(shù)來創(chuàng)建對象實(shí)例,只能通過公有靜態(tài)函數(shù)返回唯一的私有靜態(tài)變量。

三、實(shí)現(xiàn)方式

1、餓漢式

2、懶漢式

3、靜態(tài)內(nèi)部類實(shí)現(xiàn)

4、枚舉實(shí)現(xiàn)

四、餓漢式

直接實(shí)例化對象,優(yōu)點(diǎn):線程安全,能簡單實(shí)現(xiàn)單例。缺點(diǎn):JVM在加載這個(gè)類時(shí)馬上創(chuàng)建唯一單例實(shí)例,未使用則會(huì)浪費(fèi)資源。同時(shí)加大創(chuàng)建運(yùn)行負(fù)擔(dān)。


public class Singleton {

    private static Singleton singleton = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return singleton;
    }

}

五、懶漢式(雙重加鎖)


public class Singleton {

    private volatile static Singleton singleton;//volatile關(guān)鍵字禁止JVM指令重排

    private Singleton() {}

    public static Singleton getSingleton() {
        if (singleton == null) {//第一次校驗(yàn),避免實(shí)例化對象后仍然加鎖
            synchronized (Singleton.class) {
                if (singleton == null) {//第二次校驗(yàn),避免多個(gè)線程實(shí)例化對象
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

1、雙重加鎖的意義

加鎖是為了防止多個(gè)線程同時(shí)通過為空判斷,造成多次創(chuàng)建對象。第一次校驗(yàn)為了避免實(shí)例化對象以后,每一次都會(huì)走加鎖操作。第二次校驗(yàn)是防止多個(gè)線程都會(huì)進(jìn)入第一個(gè) if 語句塊內(nèi)。雖然在 if 語句塊內(nèi)有加鎖操作,但是兩個(gè)線程都會(huì)執(zhí)行 singleton =new Singleton(); 語句,只是按順序執(zhí)行,就會(huì)造成多次實(shí)例化。

2、volatile 關(guān)鍵字的意義

volatile 關(guān)鍵字主要用于禁止JVM指令重排。

singleton =new Singleton(); 創(chuàng)建對象會(huì)經(jīng)過三個(gè)步驟:

(1)分配內(nèi)存空間

(2)初始化對象

(3)將對象指向分配的內(nèi)存地址

但是JVM具有指令重排特性,有可能先執(zhí)行3再執(zhí)行2,在多線程的情況下有可能出現(xiàn)thread1執(zhí)行力3而沒有執(zhí)行2,同時(shí)thread2也進(jìn)入了,判斷singleton不為空,就將其返回,但是事實(shí)上對象還未被成功初始化。使用volatile關(guān)鍵字禁止JVM指令重排解決該問題。

六、靜態(tài)內(nèi)部類實(shí)現(xiàn)


public class Singleton {

    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

}

當(dāng) Singleton 類被加載時(shí),靜態(tài)內(nèi)部類 SingletonHolder 沒有被加載進(jìn)內(nèi)存。只有當(dāng)調(diào)用 getInstance()方法從而觸發(fā) SingletonHolder.INSTANCE 時(shí) SingletonHolder 才會(huì)被加載,此時(shí)初始化 INSTANCE 實(shí)例,并且 JVM 能確保 INSTANCE 只被實(shí)例化一次。

這種方式不僅具有延遲初始化的好處,而且由 JVM 提供了對線程安全的支持。

七、枚舉實(shí)現(xiàn)

public enum Singleton {

    INSTANCE;

    private String name;

    public void doSomething() {
        //...
    }

}

默認(rèn)枚舉實(shí)例的創(chuàng)建是線程安全的,并且在任何情況下都是單例,上述講的幾種單例模式實(shí)現(xiàn)中,有一種情況下他們會(huì)重新創(chuàng)建對象,那就是反序列化,將一個(gè)單例實(shí)例對象寫到磁盤再讀回來,從而獲得了一個(gè)實(shí)例。反序列化操作提供了readResolve方法,這個(gè)方法可以讓開發(fā)人員控制對象的反序列化。在上述的幾個(gè)方法示例中如果要杜絕單例對象被反序列化是重新生成對象,就必須加入如下方法:


    private Object readResolve()throws ObjectStreamException {
        return singleton;
    }

枚舉單例的優(yōu)點(diǎn)就是簡單,但是大部分應(yīng)用開發(fā)很少用枚舉,可讀性并不是很高。

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

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