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

定義:確保一個(gè)類只有一個(gè)實(shí)例,并提供對(duì)該實(shí)例的全局訪問(wèn),其構(gòu)造函數(shù)私有化。

單例模式的七種寫(xiě)法

1、餓漢模式
public class Singleton {
    private static Singleton instance = new Singleton();
    
    private Singleton(){
    }
    
    public static Singleton getInstance(){
        return instance;
    }
}

這種方式在類加載時(shí)就完成了初始化,所以類加載較慢,但獲取對(duì)象的速度快。這種方式基于類加載機(jī)制,避免了線程的同步問(wèn)題。在類加載的時(shí)候就完成實(shí)例化,沒(méi)有達(dá)到懶加載的效果。如果從始至終未使用過(guò)這個(gè)實(shí)例,則會(huì)造成內(nèi)存的浪費(fèi)。

2、懶漢模式
public class Singleton {
    private static Singleton instance;

    private Singleton(){
    }

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

這種懶漢模式聲明了一個(gè)靜態(tài)對(duì)象,在用戶第一次調(diào)用時(shí)初始化。這雖然節(jié)約了資源,但第一次加載時(shí)需要實(shí)例化,反應(yīng)稍慢一些,主要是在多線程時(shí)不能正常工作。

3、懶漢模式(線程安全)
public class Singleton {
    private static Singleton instance;

    private Singleton(){
    }

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

這種懶漢模式因?yàn)榧恿送芥i,所以是線程安全的,但是每次調(diào)用getInstance方法時(shí)都需要進(jìn)行同步,這會(huì)造成不必要的同步開(kāi)銷,而且大部分時(shí)候我們是用不到同步的,所以不建議用這種模式。

4、雙重檢查模式(DCL)
public class Singleton {
    private static volatile Singleton instance;

    private Singleton(){
    }

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

這種寫(xiě)法在getInstance方法中隊(duì)Singleton進(jìn)行了兩次判空:第一次是為了不必要的同步,第二次是在Singleton等于null的情況下才創(chuàng)建實(shí)例。在這里使用volatile,也會(huì)或多或少地影響性能,第一次加載時(shí)反應(yīng)稍慢,在高并發(fā)環(huán)境下也有一定的缺陷。DCL雖然在一定程度上解決了資源的消耗和多余的同步、線程安全等問(wèn)題,但其在某些情況下還是會(huì)出現(xiàn)失效的問(wèn)題。
volatile作用:
(1)保證了不同線程對(duì)這個(gè)變量進(jìn)行操作時(shí)的可見(jiàn)性,即一個(gè)線程修改了某個(gè)變量的值,這新值對(duì)其他線程來(lái)說(shuō)是立即可見(jiàn)的。
(2)禁止指令重排序優(yōu)化。(什么是指令重排序:是指CPU采用了允許將多條指令不按程序規(guī)定的順序分開(kāi)發(fā)送給各相應(yīng)電路單元處理)。
DCL失效:
當(dāng)一個(gè)A線程執(zhí)行到instance = new Singleton()語(yǔ)句時(shí),這里看起來(lái)是一句代碼,實(shí)際上這不是一個(gè)原子操作,這句代碼最終會(huì)被編譯成多條匯編指令,它大致做了3件事:
(1)給Singleton的實(shí)例分配內(nèi)存;
(2)調(diào)用Singleton()的構(gòu)造函數(shù),初始化成員字段;
(3)將instance對(duì)象指向分配的內(nèi)存空間(此時(shí)instance就不是null了)
但是,由于Java編譯器允許處理器亂序執(zhí)行,以及JDK1.5之前JMM(Java Memory Model,即java內(nèi)存模型)中Cache、寄存器到主內(nèi)存回寫(xiě)順序的規(guī)定,上面的第二和第三的順序是無(wú)法保證的。也就是說(shuō),執(zhí)行順序可能是1-2-3,也可能是1-3-2。如果是后者,并且在3執(zhí)行完畢、2未執(zhí)行之前,被切換到線程B上,這時(shí)候instance因?yàn)橐呀?jīng)在線程A內(nèi)執(zhí)行過(guò)了第三點(diǎn),instance已經(jīng)是非空了,所以,線程B直接取走instance,再使用時(shí)就會(huì)出錯(cuò),這就是DCL失效問(wèn)題。

5、靜態(tài)內(nèi)部類單例模式
public class Singleton {

    private Singleton() {
    }

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

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

第一次加載Singleton是并不會(huì)初始化INSTANCE,只有第一次調(diào)用getInstance方法是虛擬機(jī)加載SingletonHolder并初始化INSTANCE。這樣不僅能確保線程安全,也能保證Singleton類的唯一性,所以是非常推薦的一種單例寫(xiě)法。

6、枚舉單例模式
public enum Singleton {

    INSTANCE;

    public void doSomeThing() {
    }
}

默認(rèn)枚舉實(shí)例的創(chuàng)建時(shí)線程安全的,并且在任何情況下都是單例。前面講過(guò)的機(jī)制單例模式,有一種情況會(huì)重寫(xiě)創(chuàng)建對(duì)象,那就是反序列化:將一個(gè)單例實(shí)例對(duì)象寫(xiě)到磁盤(pán)再讀取回來(lái),從而獲得了一個(gè)實(shí)例。反序列化操作提供了readResolve方法,這個(gè)方法可以讓開(kāi)發(fā)者控制對(duì)象的反序列化。所以,如果要杜絕單例對(duì)象反序列化時(shí)重寫(xiě)生成對(duì)象,必須加入如下方法:

private Object readResolve() throws ObjectStreamException {
        return SingletonHolder.INSTANCE;
}

枚舉單例的優(yōu)點(diǎn)就是簡(jiǎn)單,不過(guò)Android使用enum之后的dex大小增加很多,運(yùn)行時(shí)還會(huì)產(chǎn)生額外的內(nèi)存占用,其可讀性也不高,因此官方強(qiáng)烈建議不要在Android程序里面使用到enum,枚舉單例缺點(diǎn)也很明顯。

7、容器實(shí)現(xiàn)單例模式
/**
 * 單例容器類
 */
public class SingletonManager {
    private SingletonManager() {
    }

    private static Map<String, Object> instanceMap = new HashMap<>();

    public static void registerInstance(String key, Object instance) {
        if (!instanceMap.containsKey(key)) {
            instanceMap.put(key, instance);
        }
    }

    public static Object getInstance(String key) {
        return instanceMap.get(key);
    }
}


/**
 * 單例模板
 */
public class SingletonPattern {
    SingletonPattern() {
    }

    public void doSomething() {
        Log.d("==", "doSomeing");
    }
}

// 具體用法
SingletonManager.registerInstance("SingletonPattern", new SingletonPattern());
SingletonPattern singletonPattern = (SingletonPattern) SingletonManager.getInstance("SingletonPattern");
singletonPattern.doSomething();

這種方式,根據(jù)key獲取對(duì)象對(duì)應(yīng)類型的對(duì)象,隱藏了具體實(shí)現(xià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ù)。

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

  • 前言 本文主要參考 那些年,我們一起寫(xiě)過(guò)的“單例模式”。 何為單例模式? 顧名思義,單例模式就是保證一個(gè)類僅有一個(gè)...
    tandeneck閱讀 2,627評(píng)論 1 8
  • 一.什么是單例模式 單例模式的定義:確保一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)他的全局訪問(wèn)點(diǎn)。單例模式是幾個(gè)設(shè)計(jì)模式中...
    Geeks_Liu閱讀 2,330評(píng)論 0 10
  • 概述 單例模式是應(yīng)用最廣的模式之一,在應(yīng)用這個(gè)模式時(shí),單例對(duì)象的類必須保證只有一個(gè)實(shí)例存在。許多時(shí)候整個(gè)系統(tǒng)只需要...
    劉滌生閱讀 1,107評(píng)論 0 5
  • 單例模式,顧名思義,指的是一個(gè)類只存在一個(gè)實(shí)例。 那么,如何保證某一個(gè)類只存在一個(gè)實(shí)例呢?對(duì)象的創(chuàng)建是通過(guò)類的構(gòu)造...
    哇哇哇one閱讀 353評(píng)論 0 2
  • 今天下午去參加一位朋友公司項(xiàng)目說(shuō)明會(huì),現(xiàn)在真的要出來(lái)學(xué)習(xí),世界變化太快,微信代替短信,天貓代替商場(chǎng),還有的專用詞...
    步步嬌閱讀 112評(píng)論 0 0

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