設計模式系列-單例模式

一般Java實現單例模式有不少種方式,大體可分爲懶漢模式餓漢模式,其主要區別是實例延遲加載的問題,固然單例模式每每也關注其餘問題,如:線程安全等。下面試圖來總結單例模式的這些注意點。java

代碼地址:GitHub
本文首發於:lishuo.megit

餓漢模式

public class Singleton {
    private Singleton(){}
    private static Singleton instance = new Singleton();

    public static Singleton getInstance() {
        return instance;
    }
}複製代碼

餓漢模式在類加載時候就實例化對象,使用時直接調用getInstance()方法。這個模式下,是線程安全的,在多線程併發模式下不會重複實例化對象。
缺點:對象過早的實例化,浪費系統資源。github

懶漢模式

public class Singleton {
    private Singleton(){}
    private static Singleton instance = null;

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}複製代碼

這種模式下在類加載時候並無實例化對象,而是在調用getInstance()方法。之因此使用懶漢模式,是爲了不多早的實例化對象,從而浪費系統資源。
缺點:僅適用於單線程,線程不安全。面試

改進1 - 引入synchronized

public class Singleton {
    private Singleton(){}
    private static Singleton instance = null;

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

之因此引入synchronized修飾getInstance()方法,是爲了解決線程不安全的問題。利用多線程同步機制,讓原先的線程不安全迴歸到線程安全。但引入synchronized會由於線程阻塞、切換會帶一些沒必要要的開銷,從而下降系統性能。安全

改進2 - 雙重檢查鎖定

public class Singleton {
    private Singleton(){}
    private static volatile Singleton instance = null;

    public static Singleton getInstance() {
        if (instance == null) {              // A
            synchronized (Singleton.class) {
                if (instance == null) {      // B
                    instance = new Singleton(); // C
                }
            }
        }
        return instance;
    }
}複製代碼

對比改進1中,能夠看到synchronized再也不修飾一個方法,而是縮減到修改代碼塊,由於加鎖同步的話,範圍越小,性能影響最小。多線程

這裏能夠注意到修飾變量instance的關鍵字增長了volatile。這裏volatile主要做用是提供內存屏障,禁止指令重排序。併發

現有t一、t2兩個線程同時訪問getInstance(),假設t一、t2都執行到A處。因爲有同步鎖,只能有個1個線程得到鎖,假如t1擁有該同步鎖,t1執行到C處instace = new Singleton()。將會作以下3步驟:
1.分配內存空間
2.初始化
3.將instance指向分配內存空間
正常的執行順序應爲:1->2->3。執行第3步時,這時候的instance就再也不是null了。但因爲指令重排序的存在,執行順序有可能變化爲:1->3->2。當執行3的時候,instance就再也不是null,但初始化工做有可能尚未作完。這時候若是t2獲取鎖執行的話,就會直接獲取有可能尚未初始化完成的instance。這樣使用instance會引發程序報錯。固然這也是極端狀況下,我嘗試幾回沒法捕捉重現,但並不意味着問題不存在。volatile固然仍是要加的。性能

Aif判斷做用主要是防止過可能是線程執行同步代碼塊;若是是單例模式的話,這裏同步代碼塊只會被執行一次。Bif判斷做用主要是防止多線程做用下重複實例化,保證線程安全。這也被稱爲:雙重檢查鎖定spa

雙重檢查鎖定屬於一種兼顧線程安全和性能的實現。線程

改進3 - 靜態內部類

public class Singleton {
    private Singleton(){}
    private static class Holder {
        public static Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return Holder.instance; // 執行Holder的初始化工做
    }
}複製代碼

使用靜態內部類也是懶漢模式的一種實現,當調用ggetInstance()纔會觸發加載靜態內部類,從而初始化獲取instance實例。利用靜態內部類的加載機制來保證線程安全。

枚舉方式

public enum Singleton {
    INSTANCE;
    Singleton(){}

    public Singleton getInstance() {
        return INSTANCE;
    }

}複製代碼

用枚舉方式實現單例模式,是目前比較推薦的。枚舉方式的好處是:一、線程安全;二、防止反射出現多個實例;三、防止反序列化出現多個實例。

以上是關於java單例模式的一些總結,若有紕漏,還請指出。

代碼地址:GitHub
本文首發於:lishuo.me

相關文章
相關標籤/搜索