1. 程式人生 > >單例模式(詳解)

單例模式(詳解)

@override ring 我們 真的 情況 改進 port 共享 volatil

餓漢模式
public
class HungryMode { private static HungryMode hm = new HungryMode(); private HungryMode() { System.out.println("creat hungryMode Instance"); } public static HungryMode getInstance() { return hm; } public static void ceshi() { System.out.println(
"ceshi"); } public static void main(String[] args) { HungryMode.ceshi(); } }

餓漢式單例,就是一個私有的構造方法加一個私有的靜態當前類實例對象和一個公有的靜態獲取實例方法組成由於類實例對象為靜態變量,所以在加載類的時候我們就會創建類的實例對象,這樣的話比較消耗內存,浪費性能。

懶漢模式
public
class LazyMode { private static LazyMode aLazyMode; private LazyMode() { System.out.println(
"create LazyMode Instance"); } public static LazyMode getInstance() { if(aLazyMode==null) { aLazyMode = new LazyMode(); } return aLazyMode; } public static void main(String[] args) { LazyMode.getInstance(); } }

懶漢式在餓漢式的基礎上做了改進,類實例對象做了懶加載,也就是所謂的延時加載,所以提升了一些性能。

多線程的單例
以上的寫法在單線程的編程環境中沒有什麽問題,但是如果是多個線程使用上面的單例模式就會違背單例模式的設計原則,出現多個對象,簡單的做法是在獲取類實例的方法上加同步鎖,並且給類實例對象加上volatile修飾符,volatile能保證對象的可見性,即在工作內存的內容更新能立即在主內存中可見。工作內存是線程獨有的內存,主內存是所有線程共享的內存。還有一個作用是禁止指令重排序優化。大家知道我們寫的代碼(尤其是多線程代碼),由於編譯器優化,在實際執行的時候可能與我們編寫的順序不同。編譯器只保證程序執行結果與源代碼相同,卻不保證實際指令的順序與源代碼相同。這在單線程看起來沒什麽問題,然而一旦引入多線程,這種亂序就可能導致嚴重問題。volatile關鍵字就可以從語義上解決這個問題。因為同步鎖的機制,多個線程獲取類實例對象會排隊等待獲取鎖,這樣是沒必要的,因為大多數情況下類實例對象都已經創建成功了,所以不用進入加鎖的代碼塊,於是就可以再次改進上面的代碼為雙重校驗的單例模式,如代碼所示

/**
 * 多線程的單例模式,使用雙重校驗機制
 */
public class DoubleCheckMode {

    private volatile static DoubleCheckMode sDoubleCheckMode ;

    public DoubleCheckMode() {
        System.out.println(" created " + getClass().getSimpleName());
    }

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

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread() {
                @Override
                public void run() {
                    super.run();
                    System.out.println("thread" + getId());
                    DoubleCheckMode.getInstance();
                }
            }.start();
        }
    }

}

使用靜態內部類實例單例
這種實現就是利用靜態類只會加載一次的機制,使用靜態內部類持有單例對象,達到單例的效果,直接上代碼吧

/**
 * 靜態內部類的方式實現單例,可以保證多線程的對象唯一性,還有提升性能,不用同步鎖機制
 */
public class InnerStaticMode {

    private static class SingleTonHolder {

        public static InnerStaticMode sInnerStaticMode = new InnerStaticMode();

    }

    public static InnerStaticMode getInstance(){
        return SingleTonHolder.sInnerStaticMode;
    }

}

使用枚舉實現單例

public enum  EnumMode {

    INSTANCE;

    private int id;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

總結
枚舉實現單例,不推薦在Android平臺使用,因為內存消耗會其他方式多一些,Android官方也不推薦枚舉,Android平臺推薦雙重校驗或者靜態內部類單例,現在的Android開發環境jdk一般都大於1.5了。所以volatile的問題不必擔心。Java平臺開發的Effective Java一書中推薦使用枚舉實現單例,可以保證效率,而且還能解決反序列化創建新對象的問題。

參考:你真的會寫單例模式嗎——Java實現

單例模式(詳解)