單例模式

1. 定義

單例模式:確保一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)全局訪問點(diǎn)。

  • 按照定義來看,也可以設(shè)置一個(gè)全局變量,同樣能實(shí)現(xiàn)要求,但是全局變量卻存在問題,如果將對象賦值給一個(gè)全局變量,那么必須在程序一開始就創(chuàng)建好對象,萬一這個(gè)對象非常耗費(fèi)資源,而程序在這次的執(zhí)行過程中又一直沒有用到它,就形成浪費(fèi)了。

2. 用處

有一些對象只需要一個(gè),例如配置文件,工具類,線程池,緩存,日志對象等等。單例模式保證應(yīng)用中有且只有一個(gè)實(shí)例。

常用的單例模式

(1)懶漢式:指全局的單例實(shí)例在第一次被使用時(shí)構(gòu)建。
(2)餓漢式:指全局的單例實(shí)例在類裝載時(shí)構(gòu)建。

日常我們使用的較多的應(yīng)該是懶漢式的單例,畢竟按需加載才能做到資源的最大化利用。

3. 實(shí)現(xiàn)

原理:利用靜態(tài)類變量、靜態(tài)方法和適當(dāng)?shù)?strong>訪問修飾符。

3.1. 經(jīng)典的單例模式實(shí)現(xiàn)

/**
 * 經(jīng)典的單件模式實(shí)現(xiàn)
 */
public class Singleton01 {

    // 利用一個(gè)靜態(tài)變量來記錄Singleton類的唯一實(shí)例
    private static Singleton01 uniqueInstance;

    /*
        別的成員變量
     */

    // 把構(gòu)造器聲明為私有的,只有從Singleton類內(nèi)部才可以調(diào)用構(gòu)造器
    private Singleton01() {
    }

    //
    public static Singleton01 getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton01();
        }
        return uniqueInstance;
    }

    /*
        別的方法實(shí)現(xiàn)
     */
}

上面是單例模式最經(jīng)典也是最簡單的實(shí)現(xiàn)方法,但是當(dāng)程序使用多線程的時(shí)候就會(huì)出現(xiàn)問題,如下圖所示:



在上圖中的程序就會(huì)出現(xiàn)有多個(gè)對象的情況,這是因?yàn)槭褂昧硕嗑€程,處理該多線程災(zāi)難的一個(gè)方法是把getInstance()變成同步(synchronized)方法。

3.2. 處理多線程

經(jīng)典單例遇到多線程就會(huì)創(chuàng)造出多于一個(gè)的實(shí)例,只要把getInstance()變成同步(synchronized)方法,多線程災(zāi)難就可以輕易地解決了。

/**
 * 處理多線程:
 *      經(jīng)典單例遇到多線程就會(huì)創(chuàng)造出多于一個(gè)的實(shí)例
 *      只要把getInstance()變成同步(synchronized)方法
 *      多線程災(zāi)難就可以輕易地解決了
 */
public class Singleton02 {

    // 利用一個(gè)靜態(tài)變量來記錄Singleton類的唯一實(shí)例
    private static Singleton02 uniqueInstance;

    /*
        別的成員變量
     */

    // 把構(gòu)造器聲明為私有的,只有從Singleton類內(nèi)部才可以調(diào)用構(gòu)造器
    private Singleton02() {
    }

    //
    public static synchronized Singleton02 getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton02();
        }
        return uniqueInstance;
    }

    /*
        別的方法實(shí)現(xiàn)
     */
}

但是這樣問題又來了,為了解決多線程的問題,我們引入了同步(synchronized)方法,但是同步會(huì)降低性能,這又是另一個(gè)問題。

3.3. 改善多線程

經(jīng)典單例遇到多線程會(huì)創(chuàng)造出多于一個(gè)的實(shí)例,但是使用同步(synchronized)方法又會(huì)降低性能,因此可以進(jìn)一步改善多線程。

(1)如果getInstance()的性能對應(yīng)用程序不是很關(guān)鍵,就什么都別做

(2)使用“餓漢式”創(chuàng)建實(shí)例,而不延遲實(shí)例化

這里就用到我們的“餓漢式”了,即,全局的單例實(shí)例在類裝載時(shí)構(gòu)建。
如果應(yīng)用程序總是創(chuàng)建并使用單例對象的實(shí)例,或者在創(chuàng)建和運(yùn)行時(shí)方面的負(fù)擔(dān)不太繁重,就可以使用“餓漢式”創(chuàng)建實(shí)例。

package npu.yyl.pattern.singleton;

/**
 * 使用同步(synchronized)方法會(huì)降低性能
 * 如果應(yīng)用程序總是創(chuàng)建并使用單例對象的實(shí)例,
 * 或者在創(chuàng)建和運(yùn)行時(shí)方面的負(fù)擔(dān)不太繁重,
 * 就可以使用“餓漢式”創(chuàng)建實(shí)例
 */
public class Singleton03 {

    // 在靜態(tài)初始化器(static initializer)中創(chuàng)建單例
    // 這段代碼保證了線程安全(thread safe)
    private static Singleton03 uniqueInstance = new Singleton03();

    private Singleton03() {
    }

    //
    public static Singleton03 getInstance() {
        // 已經(jīng)有實(shí)例了,直接使用它
        return uniqueInstance;
    }

    /*
        別的方法實(shí)現(xiàn)
     */
}

利用這個(gè)做法,我們依賴JVM在加載這個(gè)類時(shí)馬上創(chuàng)建此唯一的單例實(shí)例。JVM保證在任何線程訪問uniqueInstance靜態(tài)變量之前,一定先創(chuàng)建此實(shí)例。

(3)用“雙重加鎖”,在getInstance()中減少使用同步

利用雙重檢查加鎖(double-checked locking),首先檢查是否實(shí)例已經(jīng)創(chuàng)建了,如果尚未創(chuàng)建,“才”進(jìn)行同步。這樣一來,只有第一次會(huì)同步,這正是我們想要的。

package npu.yyl.pattern.singleton;

/**
 * 利用雙重檢查加鎖(double-checked locking),
 * 首先檢查是否實(shí)例已經(jīng)創(chuàng)建了,
 * 如果尚未創(chuàng)建,“才”進(jìn)行同步。
 * 這樣一來,只有第一次會(huì)同步。
 */
public class Singleton04 {

    // 利用一個(gè)靜態(tài)變量來記錄Singleton類的唯一實(shí)例
    private volatile static Singleton04 uniqueInstance;

    private Singleton04() {
    }

    public static synchronized Singleton04 getInstance() {

        if (uniqueInstance == null) {
            // 檢查實(shí)例,如果不存在,就進(jìn)入同步區(qū)塊
            synchronized (Singleton04.class) {
                // 只有第一次才徹底執(zhí)行這里的代碼
                if (uniqueInstance == null) {
                    // 進(jìn)入?yún)^(qū)塊后,再檢查一次。如果仍是null,才創(chuàng)建實(shí)例
                    uniqueInstance = new Singleton04();
                }
            }
        }
        return uniqueInstance;
    }

    /*
        別的方法實(shí)現(xiàn)
     */
}

volatile關(guān)鍵詞確保,當(dāng)uniqueInstance變量被初始化成Singleton實(shí)例時(shí),多個(gè)線程正確的處理uniqueInstance變量。
這個(gè)方法關(guān)心了性能,大大減少了getInstance()的時(shí)間消耗。

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

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

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