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

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

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

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

注意:

1、單例類只能有一個實例。

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

3、單例類必須給所有其他對象提供這一實例。

1598249715993_單例模式.jpg

單例模式介紹

意圖:保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。

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

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

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

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

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

優(yōu)點:

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

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

避免對共享資源的多重占用,簡化訪問;

為整個系統(tǒng)提供一個全局訪問點。

缺點:

不適用于變化頻繁的對象;

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

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

單例模式使用場景

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


使用單例模式時注意事項

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

單例模式的五種寫法

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

1、懶漢式

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

優(yōu)點:獲取對象的速度快,線程安全(因為虛擬機保證只會裝載一次,在裝載類的時候是不會發(fā)生并發(fā)的)

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

代碼演示:

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


2、餓漢式

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

優(yōu)點:單例只有在使用時才被實例化,一定程度上節(jié)約了資源

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

代碼演示:

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


3、雙檢鎖

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

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

代碼演示:

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)部類的方式效果類似雙檢鎖,但實現(xiàn)更簡單。但這種方式只適用于靜態(tài)域的情況,雙檢鎖方式可在實例域需要延遲初始化時使用。

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

代碼演示:

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


5、枚舉

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

優(yōu)點

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

缺點

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

代碼演示:

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


猜你喜歡:
分享到:
在線咨詢 我要報名
和我們在線交談!