1. 程式人生 > >設計模式-單例模式(7)

設計模式-單例模式(7)

單例模式的優點

1)由於單例模式在記憶體中只有一個例項, 減少了記憶體開支, 特別是一個物件需要頻繁地建立、 銷燬時, 而且建立或銷燬時效能又無法優化, 單例模式的優勢就非常明顯。
2)由於單例模式只生成一個例項, 所以減少了系統的效能開銷, 當一個物件的產生需要比較多的資源時, 如讀取配置、 產生其他依賴物件時, 則可以通過在應用啟動時直接產生一個單例物件, 然後用永久駐留記憶體的方式來解決(在Java EE中採用單例模式時需要注意JVM垃圾回收機制) 。
3)單例模式可以避免對資源的多重佔用, 例如一個寫檔案動作, 由於只有一個例項存在記憶體中, 避免對同一個資原始檔的同時寫操作。
4)單例模式可以在系統設定全域性的訪問點, 優化和共享資源訪問, 例如可以設計一個單例類, 負責所有資料表的對映處理。

單例模式的缺點

1)單例模式一般沒有介面, 擴充套件很困難, 若要擴充套件, 除了修改程式碼基本上沒有第二種途徑可以實現。 單例模式為什麼不能增加介面呢? 因為介面對單例模式是沒有任何意義的, 它要求“自行例項化”, 並且提供單一例項、 介面或抽象類是不可能被例項化的。 當然, 在特殊情況下, 單例模式可以實現介面、 被繼承等, 需要在系統開發中根據環境判斷。
2) 單例模式對測試是不利的。 在並行開發環境中, 如果單例模式沒有完成, 是不能進行測試的, 沒有介面也不能使用mock的方式虛擬一個物件。
3)單例模式與單一職責原則有衝突。 一個類應該只實現一個邏輯, 而不關心它是否是單例的, 是不是要單例取決於環境, 單例模式把“要單例”和業務邏輯融合在一個類中。

單例使用場景

如在Spring中, 每個Bean預設就是單例的, 這樣做的優點是Spring容器可以管理這些Bean的生命期, 決定什麼時候建立
出來, 什麼時候銷燬, 銷燬的時候要如何處理, 等等。 如果採用非單例模式(Prototype型別) , 則Bean初始化後的管理交由J2EE容器, Spring容器不再跟蹤管理Bean的生命週期。

單例的常用模式

餓漢模式

懶漢模式

靜態內部類模式

雙重檢測模式

餓漢模式

public class Hungry {

    private Hungry() {

    }

    /**
     * 已經自行例項化
     * 執行緒安全
     */
    private static final Hungry singleton = new Hungry();

    /**
     * 靜態工廠方法
     * @return
     */
    public static Hungry getInstance() {
        return singleton;
    }
}

懶漢模式

public class Lazy {

    private Lazy() {

    }

    private static Lazy singleton=null;

    /**
     * 靜態工廠方法
     * synchronized 加鎖執行緒安全
     * @return
     */
    public synchronized  static Lazy getInstance() {
        if (singleton == null) {
            singleton = new Lazy();
        }
        return singleton;
    }
}

靜態內部類模式

public class InnerClass {

    private InnerClass(){

    }

    /**
     * 私有靜態類
     * 執行緒安全
     */
    private static class MySelf {
        private  static final InnerClass instance = new InnerClass();
    }


    public static InnerClass getInstance() {
        return MySelf.instance;
    }
}

雙重檢測模式

public class DuplicationCheck {

    private DuplicationCheck(){

    }

    /**
     * volatile:保證記憶體可見性和防止程式碼重排
     */
    private static volatile DuplicationCheck instance;

    /**
     *  執行緒安全
     *  volatile:保證 new DuplicationCheck()時不會程式碼重排
     *  memory = allocate();  // 1:分配物件的記憶體空間
     *  ctorInstance(memory); // 2:初始化物件
     *  instance = memory;    // 3:設定instance指向剛分配的記憶體地址
     *  如果2和3重新排序會發生 instance!=null 但是物件沒初始化完畢的情況
     * @return
     */
    public static DuplicationCheck getInstance() {
        if (instance == null) {
            synchronized (DuplicationCheck.class) {
                if (instance == null) {
                    instance = new DuplicationCheck();
                }
            }
        }

        return instance;
    }

}