單例模式(3)

^^ITBOY^^發表於2019-02-14

簡介:

單例模式是一種簡單的設計模式,但是要在程式設計中使用好單例模式,卻需要注意幾個地方。

單例模式意味著在整個系統中,單例類只能有一個例項物件,且需要自行完成例項化,並始終對外提供同一個例項物件。

單例模式實現方式:

餓漢模式:

public class Singleton {
    //餓漢模式是最簡單的實現方式,在類載入時就建立單例類物件
    private static final Singleton instance = new Singleton();

    //私有化構造方法,防止外界建立物件
    private Singleton(){}

    public static Singleton newInstance(){
        //返回唯一的例項物件
        return instance;
    }
}

懶漢模式(單執行緒版):

public class Singleton {

    private static Singleton instance = null;

    //私有化構造方法,防止外界建立物件
    private Singleton(){}

    public static Singleton newInstance(){
        // 在需要的時候才去建立的單例物件,如採羊例物件已經建立,再次呼叫 ηewinstance()方法時
        // 將不會重新建立新的單例物件,而是直接返回之前建立的單例物件
        if (null == instance){
            instance = new Singleton();
        }
        //返回唯一的例項物件
        return instance;
    }
}

上述懶漢模式有延遲載入的意思,但是沒有考慮到多執行緒的情況,在多執行緒的場景中,出現多個執行緒同時呼叫newInstance方法建立單例物件,從而導致系統中出現多個單例類的例項,顯然是不符合要求的。

懶漢模式(多執行緒加鎖版):

public class Singleton {

    private static Singleton instance = null;

    //私有化構造方法,防止外界建立物件
    private Singleton(){}

    public static synchronized Singleton newInstance(){
        // 在需要的時候才去建立的單例物件,如採羊例物件已經建立,再次呼叫 ηewinstance()方法時
        // 將不會重新建立新的單例物件,而是直接返回之前建立的單例物件
        if (null == instance){
            instance = new Singleton();
        }
        //返回唯一的例項物件
        return instance;
    }
}

上述加鎖設計雖然解決了多執行緒安全的問題,但是單例在系統中只有一個例項,每次取例項都得進行加鎖解鎖操作,這會成為系統的效能瓶頸。

懶漢模式(雙重檢測鎖方式):錯誤實現

public class Singleton {

    private static Singleton instance = null;

    //私有化構造方法,防止外界建立物件
    private Singleton() {
    }

    public static Singleton newInstance() {
        if (null == instance) {//第一次檢測
            synchronized (Singleton.class) {//加鎖
                if (null == instance) {//第二次檢測
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

由於指令重排優化,可能會導致初始化單例物件和將該物件地址賦值給 instance 欄位的順 序與上面 Java 程式碼中書 寫 的順序不同 。 例如,執行緒 A 在建立單例物件時,在構造方法被呼叫 之前,就為該物件分配了記憶體空間並將物件的宇段設定為預設值。此時執行緒 A 就可以將分配的內 存地址賦值給 instance 宇段了,然而該物件可能還沒有初始化。執行緒 B 來呼叫 newInstance()方法,得 到的就 是未初始 化 完全 的單例物件,這就 會導致系統出 現異常行為 。

為了解決該問題,我們可以使用 volatile關鍵字修飾 instance欄位。 volatile關鍵字的一個語義就是禁止指令的重排序優化,從而保證 instance 欄位被初始化時,單例物件己經被完全初始化 。

懶漢模式(雙重檢測鎖方式 + volatile):

public class Singleton {

    private static volatile Singleton instance = null;

    //私有化構造方法,防止外界建立物件
    private Singleton() {
    }

    public static Singleton newInstance() {
        if (null == instance) {//第一次檢測
            synchronized (Singleton.class) {//加鎖
                if (null == instance) {//第二次檢測
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

靜態內部類方式(推薦):

public class Singleton {

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

    //私有化構造方法,防止外界建立物件
    private Singleton() {
    }

    public static Singleton newInstance() {
        return SingletonHolder.instance;
    }
}

熟悉 Java 類載入機制 的讀者知道,當第 一次訪 問類中的靜態欄位時,會觸發類載入,並且同一個類只載入一次。靜態內部類也是如此,類載入過程由類載入器負責加鎖,從而保證執行緒安全。

 

相關文章