深刻理解單例模式的幾種實現方式

前言spring

單例模式是一種很經常使用的設計模式,其定義是單例對象的類只容許有一個實例存在。在使用spring自動建立對象時默認就是單例的。編程

使用場景設計模式

須要頻繁的對對象進行建立與銷燬,若是工具類對象安全

1、餓漢式(靜態變量)多線程

public class Singleton1 {

    private static final Singleton1 INSTANCE = new Singleton1();
    //靜態工廠
    public static Singleton1 getInstance(){
        return INSTANCE;
    }

}

餓漢式就是在類加載初始化的時候就建立了對象,儘管你還不須要使用他。相對來講我是比較喜歡這種方式的,既沒有線程安全,速度也快,固然這是以犧牲空間換取速度,你們能夠根據實際狀況,根據該對象被建立的機率酌情使用該方法。ide

2、餓漢式(靜態代碼塊工具

public class Singleton2 {

    private static Singleton2 instance = null;
    //靜態代碼塊
    static {
        instance = new Singleton2();
    }

    public Singleton2 getInstance(){
        return instance;
    }

}

靜態代碼塊實現的餓漢式與上述第一種方式相似,都是在類初始化的時候就建立對象,一樣是線程安全的,其實就是JVM幫咱們避免了線程安全,由於類只要加載一次,因此只會建立一個對象。性能

3、懶漢式(多線程下不可用)優化

public class Singleton3 {

    private static Singleton3 instance = null;

    public static Singleton3 getInstance(){
        if (null == instance){       //(1)             instance = new Singleton3();   //(2)
        }
        return instance;
    }

}

 這個懶漢式寫法是線程不安全的,只能在單線程狀況下使用。假設有兩個線程A和B,線程A運行到(1)行代碼時時間片已經完了,此時CPU切換執行線程B,線程B完成整個流程,也就是說這個時候instance已經不爲空了,而後再執行線程A,此時線程A就會執行(2)行語句從新建立新的實例。spa

4、懶漢式(多線程下可用,但效率低)

public class Singleton4 {

    private static Singleton4 instance = null;

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

第四個方法與第三個方法的區別在於在getInstance方法中加了synchronized同步鎖,這樣會致使只有等一個線程運行完後另外一個線程才能進入該方法,固然不會有線程安全的問題,但咱們只是在一開始實例化對象時須要,以後只須要直接返回對象便可,這樣至關於每次都要去查詢對象是否實例化,且還要排隊查詢,這樣顯然很影響性能,至關於每一個線程都要排隊執行該方法。因此不推薦使用這種寫法。

5、靜態內部類

public class Singleton5 {

    private static class insideClass{
        private static final Singleton5 instance = new Singleton5();
    }

    public static Singleton5 getInstance(){
        return insideClass.instance;
    }

}

靜態內部類與懶漢式相似,只不過是JVM幫咱們解決了多線程的問題。在調用getInstance()方法是會加載內部類insideClass,也就會實例化instance,ClassLoader類加載機制會幫咱們保證只有一個線程去初始化該內部類,也就只會生成一個實例。看起來這個方法又是懶加載,又是線程安全好像很完美的樣子,但設計是要落地與業務的,沒有最好的寫法,只有最合適的寫法。假設建立這個對象花費的時間很長,這無疑會給初次調用getInstance()方法的用戶帶來很差的體驗,那麼這種寫法也是不可取的。

 6、雙重檢查

public class Singleton6 {

    private static volatile Singleton6 instance = null;   //(1) private Singleton6(){}

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

Double-Check在多線程編程中是很常見的。經過兩次檢查和靜態代碼塊來限制同一時間只有個線程在實例化對象,進而控制只有一個實例產生。看起來貌似很完美,但你們有沒有看到(1)行中的volatile關鍵字呢?他又有什麼做用呢?

雙重檢查的寫法在JDK1.5是有問題的,由於JVM優化會對指令進行重排序。咱們理想中建立一個對象的過程是:a->b->c

a.JVM分配內存空間

b.初始化對象

c.將instance指向分配的內存空間

但實際上執行的順序多是 a->c->b

假設有線程A和線程B,線程A執行new Singleton6(),指令執行的順序是a->c->b,此時只是將instance執行分配的內存空間,也就是說instance此時不等於null,這個時候線程B獲取instance實例去執行操做,但實際上對應的內存空間尚未初始化,那麼將會出現錯誤。

相關文章
相關標籤/搜索