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

單例模式是一種簡(jiǎn)單且最常用的設(shè)計(jì)模式,有人可能會(huì)說(shuō)我也沒(méi)有用過(guò)單例模式,那如果你是一枚可愛(ài)的Java程序猿/媛,那你肯定用過(guò)Spring吧,而Spring Bean默認(rèn)的構(gòu)建模式都是單例的,而Spring為什么會(huì)這么做呢,顯然這和單例的優(yōu)勢(shì)有關(guān)。

什么是單例模式?

屬于創(chuàng)建類型的一種常用的軟件設(shè)計(jì)模式。通過(guò)單例模式的方法創(chuàng)建的類在當(dāng)前進(jìn)程中只有一個(gè)實(shí)例(根據(jù)需要,也有可能一個(gè)線程中屬于單例,如:僅線程上下文內(nèi)使用同一個(gè)實(shí)例)(引用自百度百科)

為什么使用單例模式

在應(yīng)用或者服務(wù)運(yùn)行過(guò)程中,有些類的實(shí)例是沒(méi)有必要每次都新建的,而減少新建實(shí)例帶來(lái)的性能損耗也成為了使用單例帶來(lái)的巨大好處;比如Java在新建一個(gè)實(shí)例時(shí)則需要分配內(nèi)存,給成員變量賦值等操作,當(dāng)這種操作很頻繁,不但加大了系統(tǒng)的開(kāi)銷,也增加了觸發(fā)GC的頻率。這就如同你新找了一個(gè)對(duì)象,又要從請(qǐng)吃飯看電影,拉手,親親……開(kāi)始,傷錢傷身體。因此諸如Spring也會(huì)大量使用單例,來(lái)節(jié)省資源減少系統(tǒng)開(kāi)銷。

如何實(shí)現(xiàn)單例

實(shí)現(xiàn)單例模式通過(guò)分為懶漢和餓漢兩種方式;其中懶漢(lazy)即是在使用的時(shí)候才創(chuàng)建實(shí)例,不使用則永遠(yuǎn)不會(huì)創(chuàng)建實(shí)例,餓漢則是應(yīng)用啟動(dòng)時(shí)已經(jīng)創(chuàng)建實(shí)例,不管是否使用都會(huì)創(chuàng)建。

餓漢模式

簡(jiǎn)單餓漢模式的實(shí)現(xiàn)如下,代碼簡(jiǎn)單明了,且沒(méi)有懶漢模式的線程安全問(wèn)題,如不考慮在不使用實(shí)例的情況下占用的那一丟丟內(nèi)存,強(qiáng)烈建議如此使用

public class Singleton {

    /**
     * 聲明為static成員變量
     */
    private static Singleton INSTANCE = new Singleton();

    private Singleton() {
        //私有構(gòu)造方法
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }

}

懶漢模式

餓漢模式就更加值得研究,它雖然在不使用實(shí)例的情況下節(jié)省了系統(tǒng)開(kāi)銷(也只是一丟丟而已),但是同時(shí)帶來(lái)了一些有趣的問(wèn)題。

public class Singleton {

    private static Singleton INSTANCE = null;

    private Singleton() {
        //私有構(gòu)造方法
    }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }

}

以上是個(gè)非常簡(jiǎn)單的餓漢模式的實(shí)現(xiàn),有經(jīng)驗(yàn)的猿/媛也會(huì)發(fā)現(xiàn),這個(gè)方法存在線程安全問(wèn)題,當(dāng)多線程并發(fā)調(diào)用getInstance方法時(shí),會(huì)造成創(chuàng)建多次實(shí)例的情況,違背了單例模式設(shè)計(jì)的初衷,于是我們將其更改如下:

public class Singleton {

    private static Singleton INSTANCE = null;

    private Singleton() {
        //私有構(gòu)造方法
    }

    public static synchronized Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }

}

我們?cè)趃etIntance方法上加了synchronized,這樣則快速解決了線程安全問(wèn)題,然而追求完美的我們知道這樣做在高并發(fā)情況下會(huì)發(fā)生阻塞,它還不夠完美(沒(méi)錯(cuò)Coder就是如此的吹毛求疵),我們應(yīng)該縮小synchronized的范圍,于是產(chǎn)生了如下代碼:

public class Singleton {

    private volatile static Singleton INSTANCE = null;

    private Singleton() {
        //私有構(gòu)造方法
    }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}

這次我們縮小了synchronized的范圍,且把INSTANCE使用了volatile修飾,是使用雙重check避免了在INSTANCE不為null時(shí)還在等待鎖的問(wèn)題,但是代碼看起來(lái)有點(diǎn)難懂,是不是可能還有更加完美的方式呢,看如下代碼:

public class Singleton {

    private Singleton() {
        //私有構(gòu)造方法
    }

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

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

}

這次我們使用了靜態(tài)內(nèi)部類來(lái)實(shí)現(xiàn),根據(jù)Java classloader原理,內(nèi)部類只有在使用時(shí)才會(huì)被初始化,利用這一點(diǎn)可以實(shí)現(xiàn)線程安全的懶漢模式。

另一種模式實(shí)現(xiàn)

以上方式仍然存在問(wèn)題(畢竟我們是吹毛求疵的Coder),因?yàn)樗接邪l(fā)放并不安全,我們?nèi)匀豢梢酝ㄟ^(guò)反射使用AccessibleObject.setAccessible來(lái)創(chuàng)建的實(shí)例,同樣序列化readObject也可以做到創(chuàng)建新的實(shí)例,于是有大神發(fā)明了如下實(shí)現(xiàn)方式:

public class Singleton {

    private Singleton() {
        //私有構(gòu)造方法
    }

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

    static enum SingletonEnum {
        INSTANCE;
        private Singleton singleton = null;
        private SingletonEnum() {
            singleton = new Singleton();
        }

        public Singleton getInstance() {
            return singleton;
        }
    }

}

利用單元素的枚舉來(lái)實(shí)現(xiàn)單例模式,被稱為“實(shí)現(xiàn)Singleton的最佳方法”,但是否需要如此繁瑣復(fù)雜,需要Coder們自己考慮了。

實(shí)現(xiàn)單例模式并不是我們的目的之一,通過(guò)學(xué)習(xí)不同的實(shí)現(xiàn)方式讓我們對(duì)其他知識(shí)也有了思考,至于選擇哪種實(shí)現(xiàn)方式還是具體問(wèn)題具體分析;如果不是很處女座,餓漢模式是你不悔的選擇!

?著作權(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),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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