單例模式的終極實(shí)現(xiàn)方案

單例模式(Singleton)是一種使用率非常高的設(shè)計(jì)模式次泽,其主要目的在于保證某一類在運(yùn)行期間僅被創(chuàng)建一個(gè)實(shí)例变秦,并為該實(shí)例提供了一個(gè)全局訪問(wèn)方法成榜,通常命名為getInstance()方法。單例模式的本質(zhì)簡(jiǎn)言之即是:

控制實(shí)例數(shù)目

以Java為例蹦玫,單例模式通呈昊椋可分為餓漢式懶漢式兩種常規(guī)實(shí)現(xiàn)方式

餓漢式單例實(shí)現(xiàn)

餓漢式顧名思義,就是對(duì)類實(shí)例(食物樱溉?)的需求非常強(qiáng)烈挣输,因此,在裝載該單例類的時(shí)候就會(huì)創(chuàng)建類實(shí)例福贞。如下

public class Singleton {
    /**
     * 裝載時(shí)即創(chuàng)建類實(shí)例撩嚼,并保存在類變量instance中
     * 加上static關(guān)鍵詞使得該變量能在getInstance()靜態(tài)方法中使用
     */
    private static Singleton instance = new Singleton();
 
    /**
     * 私有化構(gòu)造方法,使外部無(wú)法通過(guò)構(gòu)造方法構(gòu)造除instance外的類實(shí)例
     * 從而達(dá)到單例模式控制類實(shí)例數(shù)目的目的
     */
    private Singleton() {
    }
 
    /**
     * 類實(shí)例的全局訪問(wèn)方法
     * 加上static關(guān)鍵詞使得外部可以通過(guò)類名直接調(diào)用該方法獲取類實(shí)例
     * @return 單例類實(shí)例
     */
    public static Singleton getInstance() {
        //  由于類實(shí)例在類裝載時(shí)已被創(chuàng)建并保存在instance中,因此可直接返回
        return instance;
    }
}

事實(shí)上完丽,在Android開(kāi)發(fā)中恋技,Android Studio提供了一個(gè)直接創(chuàng)建單例類的功能(File->new->Singleton),該功能自動(dòng)生成的單例類正是采用了餓漢式的實(shí)現(xiàn)方式

懶漢式單例實(shí)現(xiàn)

說(shuō)到懶逻族,我們自然而然會(huì)想到拖延癥這一惡習(xí)蜻底,這一點(diǎn)和懶漢式的單例實(shí)現(xiàn)方式相似,這一實(shí)現(xiàn)方式會(huì)一直等到真正需要使用對(duì)象實(shí)例的時(shí)候再去創(chuàng)建該實(shí)例聘鳞。如下

public class Singleton {
    /**
     * 裝載時(shí)不創(chuàng)建類實(shí)例薄辅,但需要利用一個(gè)類變量去保存后續(xù)創(chuàng)建的類實(shí)例
     * 添加static關(guān)鍵詞使得該變量能在getInstance()靜態(tài)方法中使用
     */
    private static Singleton instance = null;
 
    /**
     * 私有化構(gòu)造方法,使外部無(wú)法通過(guò)構(gòu)造方法構(gòu)造除instance外的類實(shí)例
     * 從而達(dá)到單例模式控制類實(shí)例數(shù)目的目的
     */
    private Singleton() {
    }
 
    /**
     * 類實(shí)例的全局訪問(wèn)方法
     * 添加static關(guān)鍵詞使得外部可以通過(guò)類名直接調(diào)用該方法獲取類實(shí)例
     * @return 單例類實(shí)例
     */
    public static Singleton getInstance() {
        //  如果instance未被初始化抠璃,則初始化該類實(shí)例
        if (instance == null) {
            instance = new Singleton();
        }
 
        return instance;
    }
}

事實(shí)上站楚,雖然我們前面拿拖延癥來(lái)與懶漢式做類比,但懶漢式的拖延卻是實(shí)際開(kāi)發(fā)中的一種較為常見(jiàn)的節(jié)省資源的方式鸡典,即延遲加載思想源请。這一思想的核心在于直到需要使用某些資源或數(shù)據(jù)時(shí)再去加載該資源或獲取該數(shù)據(jù),這樣可以盡可能地節(jié)省使用前的內(nèi)存空間

線程安全的懶漢式單例實(shí)現(xiàn)

不難分析出彻况,當(dāng)外部多個(gè)線程同時(shí)想要獲取單例類實(shí)例時(shí)谁尸,上述懶漢式實(shí)現(xiàn)方式便很容易導(dǎo)致并發(fā)問(wèn)題。通常有如下幾種改進(jìn)方式

添加synchronized關(guān)鍵詞

....
public static synchronized Singleton getInstance() {
....

這種改進(jìn)方式是最簡(jiǎn)單的纽甘,但由于外部每次調(diào)用getInstance()方法時(shí)均需進(jìn)行判斷良蛮,因此該方式也是效率較低的

利用雙重檢查加鎖機(jī)制

雙重檢查加鎖機(jī)制分為如下兩重檢查

  • 在程序每次調(diào)用getInstance()方法時(shí)先不進(jìn)行同步,而是在進(jìn)入該方法后再去檢查類實(shí)例是否存在悍赢,若不存在則進(jìn)入接下來(lái)的同步代碼塊
  • 進(jìn)入同步代碼塊后將再次檢查類實(shí)例是否存在决瞳,若不存在則創(chuàng)建一個(gè)新的實(shí)例

這樣一來(lái),就只需要在類實(shí)例初始化時(shí)進(jìn)行一次同步判斷即可左权,而非每次調(diào)用getInstance()方法時(shí)都進(jìn)行同步判斷皮胡,大大節(jié)省了時(shí)間,具體實(shí)現(xiàn)如下

public class Singleton {
    /**
     * 裝載時(shí)不創(chuàng)建類實(shí)例赏迟,但需要利用一個(gè)類變量去保存后續(xù)創(chuàng)建的類實(shí)例
     * 添加volatile關(guān)鍵詞使其不會(huì)被本地線程緩存屡贺,保證線程能正確處理
     * 添加static關(guān)鍵詞使得該變量能在getInstance()靜態(tài)方法中使用
     */
    private volatile static Singleton instance = null;
 
    /**
     * 私有化構(gòu)造方法,使外部無(wú)法通過(guò)構(gòu)造方法構(gòu)造除instance外的類實(shí)例
     * 從而達(dá)到單例模式控制類實(shí)例數(shù)目的目的
     */
    private Singleton() {
    }
 
    /**
     * 類實(shí)例的全局訪問(wèn)方法
     * 添加static關(guān)鍵詞使得外部可以通過(guò)類名直接調(diào)用該方法獲取類實(shí)例
     * @return 單例類實(shí)例
     */
    public static Singleton getInstance() {
        //  第一重檢查:如果instance未被初始化锌杀,則進(jìn)入同步代碼塊
        if (instance == null) {
            //  同步代碼塊甩栈,保證線程安全
            synchronized (Singleton.class) {
                //  第二重檢查:如果instance未被初始化,則初始化該類實(shí)例
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
 
        return instance;
    }
}

利用Java緩存思想實(shí)現(xiàn)的單例實(shí)現(xiàn)

public class Singleton {
    //  類實(shí)例緩存KEY值
    private static final String KEY = "CACHE";
 
    //  類實(shí)例緩存容器
    private static Map<String, Singleton> map = new HashMap<>();
 
    /**
     * 私有化構(gòu)造方法糕再,使外部無(wú)法通過(guò)構(gòu)造方法構(gòu)造除instance外的類實(shí)例
     * 從而達(dá)到單例模式控制類實(shí)例數(shù)目的目的
     */
    private Singleton() {
    }
 
    /**
     * 類實(shí)例的全局訪問(wèn)方法
     * 添加static關(guān)鍵詞使得外部可以通過(guò)類名直接調(diào)用該方法獲取類實(shí)例
     * @return 單例類實(shí)例
     */
    public static Singleton getInstance() {
        //  嘗試從緩存容器中獲取類實(shí)例
        Singleton instance = map.get(KEY);
        //  未能獲取類實(shí)例量没,則初始化該實(shí)例,并將其緩存至容器中
        if (instance == null) {
            instance = new Singleton();
            map.put(KEY, instance);
        }
 
        return instance;
    }
}

上述實(shí)現(xiàn)方式暫未考慮線程安全問(wèn)題突想。事實(shí)上殴蹄,利用緩存來(lái)實(shí)現(xiàn)的單例模式其最大的優(yōu)點(diǎn)在于對(duì)單例模式進(jìn)行擴(kuò)展究抓。我們自然而然地可以想到這么一種情況,既然在實(shí)際開(kāi)發(fā)中經(jīng)常需要保證某個(gè)類只能被創(chuàng)建一個(gè)實(shí)例饶套,那么漩蟆,會(huì)不會(huì)出現(xiàn)保證某個(gè)類只能被創(chuàng)建兩個(gè)或多個(gè)實(shí)例這種需求呢?對(duì)于這項(xiàng)需求妓蛮,我們首先可以想到怠李,上述實(shí)現(xiàn)方式中所建立的緩存容器是可以存儲(chǔ)多個(gè)類實(shí)例的,利用這一特點(diǎn)蛤克,只需考慮一個(gè)問(wèn)題捺癞,即外部調(diào)用時(shí)到底需要為其返回哪一個(gè)實(shí)例,便可實(shí)現(xiàn)“雙例模式”以及“多例模式”(原諒我為它們?nèi)×艘恍┢婀值拿郑┝斯辜罚唧w實(shí)現(xiàn)如下

public class Singleton {
    //  可創(chuàng)建的最大類實(shí)例數(shù)髓介,這里以“雙例模式”為例
    private static final int MAX = 2;
 
    //  類實(shí)例緩存KEY值
    private static final String KEY = "CACHE";
 
    //  當(dāng)前正在使用的實(shí)例序號(hào)
    private static int index = 1;
 
    //  類實(shí)例緩存容器
    private static Map<String, Singleton> map = new HashMap<>();
 
    /**
     * 私有化構(gòu)造方法,使外部無(wú)法通過(guò)構(gòu)造方法構(gòu)造除instance外的類實(shí)例
     * 從而達(dá)到單例模式控制類實(shí)例數(shù)目的目的
     */
    private Singleton() {
    }
 
    /**
     * 類實(shí)例的全局訪問(wèn)方法
     * 添加static關(guān)鍵詞使得外部可以通過(guò)類名直接調(diào)用該方法獲取類實(shí)例
     * @return 單例類實(shí)例
     */
    public static Singleton getInstance() {
        //  嘗試從緩存容器中獲取第index個(gè)類實(shí)例
        String key = KEY + index;
        Singleton instance = map.get(key);
        //  未能獲取類實(shí)例筋现,則初始化該實(shí)例唐础,并將其緩存至容器相應(yīng)index中
        if (instance == null) {
            instance = new Singleton();
            map.put(key, instance);
        }
 
        //  這里以最基本的順序調(diào)用為例,其他復(fù)雜調(diào)度方式不加討論矾飞,具體調(diào)用方式如下
        //  index++一膨,以在下一次調(diào)用中獲取下一個(gè)類實(shí)例,當(dāng)達(dá)到類實(shí)例數(shù)上限時(shí)洒沦,重新獲取第一個(gè)類實(shí)例
        if ((++index) > MAX) {
            index = 1;
        }
 
        return instance;
    }
}

單例模式的最佳實(shí)現(xiàn)

綜合而言豹绪,上述實(shí)現(xiàn)方式都或多或少地存在諸如線程不安全、無(wú)法做到延遲加載等小缺陷申眼。這里給出一個(gè)可以稱得上完美的最佳解決方案

Lazy Initialization Holder Class 模式

這一方案的核心在于Java的類級(jí)內(nèi)部類(即使用static關(guān)鍵詞修飾的內(nèi)部類瞒津,否則稱之為對(duì)象級(jí)內(nèi)部類)以及多線程缺省同步鎖,先來(lái)看看具體實(shí)現(xiàn)

public class Singleton {
    /**
     * 類級(jí)內(nèi)部類括尸,用于緩存類實(shí)例
     * 該類將在被調(diào)用時(shí)才會(huì)被裝載巷蚪,從而實(shí)現(xiàn)了延遲加載
     * 同時(shí)由于instance采用靜態(tài)初始化的方式,因此JVM能保證其線程安全性
     */
    private static class Instance {
        private static Singleton instance = new Singleton();
    }
 
    /**
     * 私有化構(gòu)造方法濒翻,使外部無(wú)法通過(guò)構(gòu)造方法構(gòu)造除instance外的類實(shí)例
     * 從而達(dá)到單例模式控制類實(shí)例數(shù)目的目的
     */
    private Singleton() {
    }
 
    /**
     * 類實(shí)例的全局訪問(wèn)方法
     * 添加static關(guān)鍵詞使得外部可以通過(guò)類名直接調(diào)用該方法獲取類實(shí)例
     * @return 單例類實(shí)例
     */
    public static Singleton getInstance() {
        return Instance.instance;
    }
}

在前面提到的餓漢式實(shí)現(xiàn)方式中钓辆,我們利用Java的靜態(tài)初始化、借由JVM實(shí)現(xiàn)了線程安全肴焊,因此這里同樣采用了這種方式。而另一方面功戚,為了避免餓漢式實(shí)現(xiàn)中無(wú)法進(jìn)行延遲加載的缺陷娶眷,我們構(gòu)造了一個(gè)類級(jí)內(nèi)部類來(lái)緩存類實(shí)例,由于該類只會(huì)在通過(guò)getInstance()方法去調(diào)用時(shí)才會(huì)被系統(tǒng)裝載啸臀,換言之届宠,只有初次調(diào)用getInstance()方法時(shí)才會(huì)去初始化類實(shí)例烁落,因此也實(shí)現(xiàn)了延遲加載這一功能。如此便可使得這一實(shí)現(xiàn)方式能夠同時(shí)具備線程安全豌注、延遲加載以及節(jié)省大量同步判斷資源等優(yōu)勢(shì)伤塌,可以說(shuō)是單例模式的最佳實(shí)現(xiàn)了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市轧铁,隨后出現(xiàn)的幾起案子每聪,更是在濱河造成了極大的恐慌,老刑警劉巖齿风,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件药薯,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡救斑,警方通過(guò)查閱死者的電腦和手機(jī)童本,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)脸候,“玉大人穷娱,你說(shuō)我怎么就攤上這事≡寺伲” “怎么了泵额?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)茶袒。 經(jīng)常有香客問(wèn)我梯刚,道長(zhǎng),這世上最難降的妖魔是什么薪寓? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任亡资,我火速辦了婚禮,結(jié)果婚禮上向叉,老公的妹妹穿的比我還像新娘锥腻。我一直安慰自己,他們只是感情好母谎,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布瘦黑。 她就那樣靜靜地躺著,像睡著了一般奇唤。 火紅的嫁衣襯著肌膚如雪幸斥。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天咬扇,我揣著相機(jī)與錄音甲葬,去河邊找鬼。 笑死懈贺,一個(gè)胖子當(dāng)著我的面吹牛经窖,可吹牛的內(nèi)容都是我干的坡垫。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼画侣,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼冰悠!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起配乱,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤溉卓,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后宪卿,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體的诵,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年佑钾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了西疤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡休溶,死狀恐怖代赁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情兽掰,我是刑警寧澤芭碍,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布放钦,位于F島的核電站详民,受9級(jí)特大地震影響矛洞,放射性物質(zhì)發(fā)生泄漏龙屉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一码耐、第九天 我趴在偏房一處隱蔽的房頂上張望抄伍。 院中可真熱鬧筷频,春花似錦熏挎、人聲如沸速勇。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)烦磁。三九已至,卻和暖如春哼勇,著一層夾襖步出監(jiān)牢的瞬間都伪,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工积担, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留陨晶,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓磅轻,卻偏偏與公主長(zhǎng)得像珍逸,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子聋溜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容