首頁技術(shù)文章正文

單例模式介紹:懶漢和餓漢代碼

更新時(shí)間:2020-08-24 來源:黑馬程序員 瀏覽量:

單例模式(Singleton Pattern)顧名思義就是只有一個(gè)實(shí)例,是一種常用的軟件設(shè)計(jì)模,設(shè)計(jì)模式屬于創(chuàng)建型模式它提供了一種創(chuàng)建對(duì)象的最佳方式,但是在Java中要用好單例模式,并不是一件簡(jiǎn)單的事。在整個(gè)系統(tǒng)中,單例類只能有一個(gè)實(shí)例對(duì)象,且需要自行完成示例,并始終對(duì)外提供同一實(shí)例對(duì)象。

注意:

1、單例類只能有一個(gè)實(shí)例。

2、單例類必須自己創(chuàng)建自己的唯一實(shí)例。

3、單例類必須給所有其他對(duì)象提供這一實(shí)例。

1598249715993_單例模式.jpg

單例模式介紹

意圖:保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn)。

主要解決:一個(gè)全局使用的類頻繁地創(chuàng)建與銷毀。

何時(shí)使用:當(dāng)您想控制實(shí)例數(shù)目,節(jié)省系統(tǒng)資源的時(shí)候。

如何解決:判斷系統(tǒng)是否已經(jīng)有這個(gè)單例,如果有則返回,如果沒有則創(chuàng)建。

關(guān)鍵代碼:構(gòu)造函數(shù)是私有的。

單例模式的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

在內(nèi)存中只有一個(gè)對(duì)象,節(jié)省內(nèi)存空間;

避免頻繁的創(chuàng)建銷毀對(duì)象,可以提高性能;

避免對(duì)共享資源的多重占用,簡(jiǎn)化訪問;

為整個(gè)系統(tǒng)提供一個(gè)全局訪問點(diǎn)。

缺點(diǎn):

不適用于變化頻繁的對(duì)象;

濫用單例將帶來一些負(fù)面問題,如為了節(jié)省資源將數(shù)據(jù)庫連接池對(duì)象設(shè)計(jì)為的單例類,可能會(huì)導(dǎo)致共享連接池對(duì)象的程序過多而出現(xiàn)連接池溢出;

如果實(shí)例化的對(duì)象長(zhǎng)時(shí)間不被利用,系統(tǒng)會(huì)認(rèn)為該對(duì)象是垃圾而被回收,這可能會(huì)導(dǎo)致對(duì)象狀態(tài)的丟失;

單例模式使用場(chǎng)景

如果我們?cè)诖a中需要一個(gè)全局類,我們可以讓它變成一個(gè)單例。例如,我們?cè)谙到y(tǒng)的多個(gè)地方需要讀取一個(gè)配置文件,我們并不需要每次都去new一個(gè)實(shí)例,然后去讀文件,只需要維護(hù)一個(gè)全局的Config類,并且每次使用的時(shí)候校驗(yàn)下文件是否變更即可。依賴可以減少類的創(chuàng)建跟銷毀的時(shí)候的開銷,二來也減少了讀取文件的次數(shù)。又如我們需要維護(hù)一個(gè)計(jì)數(shù)器,我們當(dāng)然不想每次統(tǒng)計(jì)的時(shí)候都穿透去寫DB,我們可以先寫到內(nèi)存當(dāng)中。還有,在程序開發(fā)中,我們常常運(yùn)用到各種池化技術(shù),我們可以將線程池,連接池作為一個(gè)單例,統(tǒng)一進(jìn)行分配跟管理


使用單例模式時(shí)注意事項(xiàng)

單例模式在多線程的應(yīng)用場(chǎng)合下必須小心使用。如果當(dāng)唯一實(shí)例尚未創(chuàng)建時(shí),有兩個(gè)線程同時(shí)調(diào)用創(chuàng)建方法,那么它們同時(shí)沒有檢測(cè)到唯一實(shí)例的存在,從而同時(shí)各自創(chuàng)建了一個(gè)實(shí)例,這樣就有兩個(gè)實(shí)例被構(gòu)造出來,從而違反了單例模式中實(shí)例唯一的原則。解決這個(gè)問題的辦法是為指示類是否已經(jīng)實(shí)例化的變量提供一個(gè)互斥鎖(雖然這樣會(huì)降低效率)。

單例模式的五種寫法

單例模式有很多種寫法,但是有些寫法在特定的場(chǎng)景下,尤其是多線程條件下,無法滿足實(shí)現(xiàn)單一實(shí)例對(duì)象的要求,從而導(dǎo)致錯(cuò)誤。下面我們來介紹單例模式的五種寫法。

1、懶漢式

懶漢式,顧名思義就是實(shí)例在用到的時(shí)候才去創(chuàng)建,“比較懶”,用的時(shí)候才去檢查有沒有實(shí)例,如果有則返回,沒有則新建。有線程安全和線程不安全兩種寫法,區(qū)別就是synchronized關(guān)鍵字。

優(yōu)點(diǎn):獲取對(duì)象的速度快,線程安全(因?yàn)樘摂M機(jī)保證只會(huì)裝載一次,在裝載類的時(shí)候是不會(huì)發(fā)生并發(fā)的)

缺點(diǎn):耗內(nèi)存(若類中有靜態(tài)方法,在調(diào)用靜態(tài)方法的時(shí)候類就會(huì)被加載,類加載的時(shí)候就完成了單例的初始化,拖慢速度)。

代碼演示:

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


2、餓漢式

餓漢式,從名字上也很好理解,就是“比較勤”,實(shí)例在初始化的時(shí)候就已經(jīng)建好了,不管你有沒有用到,都先建好了再說。

優(yōu)點(diǎn):?jiǎn)卫挥性谑褂脮r(shí)才被實(shí)例化,一定程度上節(jié)約了資源

缺點(diǎn):加入synchronized關(guān)鍵字,造成不必要的同步開銷。不建議使用。

代碼演示:

// 懶漢式單例
public class Singleton2 {
 
    // 指向自己實(shí)例的私有靜態(tài)引用
    private static Singleton2 singleton2;
 
    // 私有的構(gòu)造方法
    private Singleton2(){}
 
    // 以自己實(shí)例為返回值的靜態(tài)的公有方法,靜態(tài)工廠方法
    public static Singleton2 getSingleton2(){
        // 被動(dòng)創(chuàng)建,在真正需要使用時(shí)才去創(chuàng)建
        if (singleton2 == null) {
            singleton2 = new Singleton2();
        }
        return singleton2;
    }
}


3、雙檢鎖

雙檢鎖,又叫雙重校驗(yàn)鎖,綜合了懶漢式和餓漢式兩者的優(yōu)缺點(diǎn)整合而成。看上面代碼實(shí)現(xiàn)中,特點(diǎn)是在synchronized關(guān)鍵字內(nèi)外都加了一層 if 條件判斷,這樣既保證了線程安全,又比直接上鎖提高了執(zhí)行效率,還節(jié)省了內(nèi)存空間。

優(yōu)點(diǎn):線程安全;延遲加載;效率較高。

代碼演示:

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}


4、靜態(tài)內(nèi)部類

靜態(tài)內(nèi)部類的方式效果類似雙檢鎖,但實(shí)現(xiàn)更簡(jiǎn)單。但這種方式只適用于靜態(tài)域的情況,雙檢鎖方式可在實(shí)例域需要延遲初始化時(shí)使用。

優(yōu)點(diǎn):避免了線程不安全,延遲加載,效率高。

代碼演示:

//阻止發(fā)生派生,而派生可能會(huì)增加實(shí)例
public sealed class Singleton{
    //在第一次引用類的任何成員時(shí)創(chuàng)建實(shí)例,公共語言運(yùn)行庫負(fù)責(zé)處理變量初始化
    private static readonly Singleton instance=new Singleton();
    
    private Singleton() { }
    public static Singleton GetInstance(){
        return instance;
    }
}


5、枚舉

枚舉的方式是比較少見的一種實(shí)現(xiàn)方式,但是看上面的代碼實(shí)現(xiàn),卻更簡(jiǎn)潔清晰。并且她還自動(dòng)支持序列化機(jī)制,絕對(duì)防止多次實(shí)例化。

優(yōu)點(diǎn)

系統(tǒng)內(nèi)存中該類只存在一個(gè)對(duì)象,節(jié)省了系統(tǒng)資源,對(duì)于一些需要頻繁創(chuàng)建銷毀的對(duì)象,使用單例模式可以提高系統(tǒng)性能。

缺點(diǎn)

當(dāng)想實(shí)例化一個(gè)單例類的時(shí)候,必須要記住使用相應(yīng)的獲取對(duì)象的方法,而不是使用new,可能會(huì)給其他開發(fā)人員造成困擾,特別是看不到源碼的時(shí)候。

代碼演示:

public enum Singleton {
    INSTANCE;
    public void whateverMethod() {
    }
}


猜你喜歡:
分享到:
在線咨詢 我要報(bào)名
和我們?cè)诰€交談!