1. 程式人生 > 實用技巧 >設計模式:單例模式

設計模式:單例模式

一.

  單例模式指確保一個類在任何情況下都絕對只有一個例項,並提供一個全域性訪問點。

二.餓漢模式

public class HungrySingleton {

    private HungrySingleton(){}

    private final static HungrySingleton HUNGRY_SINGLETON ;

    static {
        HUNGRY_SINGLETON = new HungrySingleton();
    }

    public static HungrySingleton getInstance(){
        return HUNGRY_SINGLETON;
    }
}

 餓漢式是指類的被載入的時候,就被初始化,並建立單例物件,不會存在訪問安全問題。

缺點:所有的餓漢單例物件都會在專案啟動時初始化,會造成大量記憶體資源浪費。

三.懶漢模式

(1)考慮到餓漢模式的缺點後,加以修改,在類被呼叫的時候,才初始化。

public class LazySimpleSingleton {

    private LazySimpleSingleton(){}

    private static LazySimpleSingleton LAZY_SIMPLE_SINGLETON = null;

    public static LazySimpleSingleton getIn,stance(){
        if(null==LAZY_SIMPLE_SINGLETON){
            LAZY_SIMPLE_SINGLETON = new LazySimpleSingleton();
            return LAZY_SIMPLE_SINGLETON;
        }
        return LAZY_SIMPLE_SINGLETON;
    }
}

 但是會帶來一個新的問題,就是在多執行緒環境下,有兩個執行緒同一時間進入getInstance方法,同事滿足null=LAZY_SIMPLE_SINGLETON時,會建立兩個物件,然後後建立的會覆蓋先建立的單例物件。

考慮到這個問題後,進一步優化,使用synchronized關鍵字,給方法加鎖。

public class LazySimpleSingleton {

    private LazySimpleSingleton(){}

    private static LazySimpleSingleton LAZY_SIMPLE_SINGLETON = null;

    public synchronized static LazySimpleSingleton getInstance(){
        if(null==LAZY_SIMPLE_SINGLETON){
            LAZY_SIMPLE_SINGLETON = new LazySimpleSingleton();
            return LAZY_SIMPLE_SINGLETON;
        }
        return LAZY_SIMPLE_SINGLETON;
    }
}

 當一個執行緒呼叫getInstance方法時,另一個執行緒也呼叫該方法,會出現阻塞,直到第一個執行緒執行結束,才繼續呼叫,完美解決了執行緒安全問題。

出現新的問題:

如果執行緒數量暴增,給getInstatnce加鎖,只有一個執行緒執行該方法,其他執行緒全部阻塞等待,使用者體驗不好。新的解決方案--雙重檢查鎖單例寫法應運而生。

(2)雙重檢查鎖單例 (進門安檢一次,閘口再檢查一次)

改造一下寫法:

public class LazyDoubleSingleton {
    private volatile static LazyDoubleSingleton instance;
    private LazyDoubleSingleton(){}

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

 這樣的寫法,其實和上一種寫法差不多,都會造成大量執行緒阻塞。那如果把if條件往上升一級呢,先判斷,再加鎖。

public class LazyDoubleSingleton {
    private volatile static LazyDoubleSingleton instance;
    private LazyDoubleSingleton(){}

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

 經過此次修改後,還是會出現執行緒安全問題。

因為當兩個執行緒同事滿足null==instance條件後,會執行sychronized程式碼塊的程式碼,該物件還是會被建立兩次。

再優化一下,我們在sychronized程式碼塊中再進行物件的非空檢查,這樣該物件就不會被建立兩次。

public class LazyDoubleSingleton {
    private volatile static LazyDoubleSingleton instance;
    private LazyDoubleSingleton(){}

    public static LazyDoubleSingleton getInstance(){
        //檢查是否要阻塞
        if(null==instance){
            synchronized (LazyDoubleSingleton.class){
                //檢查是否要建立物件
                if(null==instance){
                    instance = new LazyDoubleSingleton();
                }

            }
        }
        return instance;
    }
}

四.靜態內部類單例的寫法

雙重檢查鎖單例這種方式雖然解決了執行緒安全問題和效能問題,但是用到了sychronized總是要上鎖,對效能還是有一些影響。我們可以採用靜態內部類的方式進行優化。

/**
 * @Author wen.jie
 * @Description 使用InnerClassSingleton類時,會預設先初始化內部類,如果沒有使用,則內部類不初始化
 **/
public class InnerClassSingleton {

    private InnerClassSingleton(){}

    public static InnerClassSingleton getInstance(){
        return InnerClass.INNER_CLASS_SINGLETON;
    }

    private static class InnerClass{
        private static final InnerClassSingleton INNER_CLASS_SINGLETON = new InnerClassSingleton();
    }
}

 這種方式兼顧了餓漢單例寫法的記憶體浪費問題和sychronized的效能問題,內部類一定要在方法呼叫之前就被初始化,巧妙的避開了執行緒安全問題。靜態內部類單例寫法真的完美了嗎?(未完待續)