簡單卻又複雜的單例模式

單例模式

定義:保證一個類僅有一個實例,並提供一個全局的訪問點

使用場景

  • 想確保任何情況下都絕對只有一個實例
  • 當你想控制實例數目,節省系統資源的時候

優點

  • 在內存裏只有一個實例,減少了內存的開銷,尤其是頻繁的創建和銷燬實例(比如管理學院首頁頁面緩存)。
  • 避免對資源的多重佔用(比如寫文件操作)
  • 設置全局訪問點,嚴格控制訪問

缺點

  • 沒有接口,不能繼承
  • 與單一職責原則衝突,一個類應該只關心內部邏輯,而不關心外面怎麼樣來實例化

注意事項

  • 私有構造器
  • 線程安全
  • 延遲加載(在第一次需要的時候再創建實例,避免不必要的內存浪費)
  • 序列化和反序列化安全(需要在單例類裏,添加readResolve()方法)
  • 防止反射攻擊(解決辦法:在私有構造器裏面,添加判斷,拋出異常)

不同版本的單例模式

1.餓漢式:在類加載的時候就new出一個可供全局的實例

public class HungrySingleton implements Serializable {
    private final static HungrySingleton hungrySingleton;

    static {
        hungrySingleton = new HungrySingleton();
    }

    private HungrySingleton(){  // 防止反射創建新的實例
        if(hungrySingleton != null){
            throw new RuntimeException("單例模式禁止反射調用");
        }
    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }

    private Object readResolve(){ //序列化和反序列化安全
        return hungrySingleton;
    }
}

2.懶漢式:延遲加載,在第一次需要的時候,創建實例

public class LazySingleton {
    private static LazySingleton lazySingleton = null;
    
    private LazySingleton(){
        if(lazySingleton != null){
            throw new RuntimeException("單例模式禁止反射調用");
        }
    }
    
    public synchronized static LazySingleton getInstance(){ // 線程安全
        if (lazySingleton == null){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

3.懶漢式之雙重校檢鎖:

public class LazyDoubleCheckSingleton {

    /**
     * 注意實例變量要加volatile修飾,防止指令重排
     * 新建一個對象實例有三個步驟:
     *    1.分配內存給這個對象
     *    2.初始化對象
     *    3.設置 lazyDoubleCheckSingleton 指向剛分配的內存地址
     * 但是 JVM 可能會對編譯的指令進行重排序,如:上面的三個步驟順序可能變爲 1->3->2
     * 當一個線程 Thread1 新建實例時,執行到上面重排序的順序步驟 3 (此時還未執行2,即還沒有初始化)
     * 而另一個線程 Thread2 調用 getInstance() 方法時,發現 lazyDoubleCheckSingleton 已經創建,不爲空
     * 所以 Thread2 就會引用 lazyDoubleCheckSingleton ,但此時實例還未初始化,會報錯
     */
    private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;

    private LazyDoubleCheckSingleton(){
        if(lazyDoubleCheckSingleton != null){
            throw new RuntimeException("單例模式禁止反射調用");
        }
    }
    
    public static LazyDoubleCheckSingleton getInstance(){
        if (lazyDoubleCheckSingleton == null){
            synchronized (LazyDoubleCheckSingleton.class){
                if(lazyDoubleCheckSingleton == null){
                    lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
                    //1.分配內存給這個對象
                    //2.初始化對象
                    //3.設置 lazyDoubleCheckSingleton 指向剛分配的內存地址
                }
            }
        }
        return lazyDoubleCheckSingleton;
    }

}

4.枚舉類單例模式,推薦使用

public enum EnumInstance {
    INSTANCE;
    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public static EnumInstance getInstance(){
        return INSTANCE;
    }
}

5.靜態內部類,推薦使用

public class StaticInnerClassSingleton {
    
    private StaticInnerClassSingleton(){
        if(InnerClass.staticInnerClassSingleton != null){
            throw new RuntimeException("單例模式禁止反射調用");
        }
    }

    private static class InnerClass{
        private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
    }

    public static StaticInnerClassSingleton getInstance(){
        return InnerClass.staticInnerClassSingleton;
    }
    
}

6.容器式單例模式

public class ContainerSingleton {

    private ContainerSingleton(){
        
    }

    private static Map<String,Object> singletonMap = new HashMap<String, Object>();

    public static void putInstance(String key,Object instance){
        if (StringUtils.isNotBlank(key) && instance != null){
            if(!singletonMap.containsKey(key)){
                singletonMap.put(key,instance);
            }
        }
    }

    public static Object getInstance(String key){
        return singletonMap.get(key);
    }
}

7.使用 ThreadLocal 的單例模式

/**
* ThreadLocal 多個線程之間是不同的實例,但是線程裏面實例唯一
**/
public class ThreadLocalInstance {

    private static final ThreadLocal<ThreadLocalInstance> threadLocalInstanceThreadLocal = new ThreadLocal<ThreadLocalInstance>(){
        @Override
        protected ThreadLocalInstance initialValue() {
            return new ThreadLocalInstance();
        }
    };

    private ThreadLocalInstance(){

    }

    public static ThreadLocalInstance getInstance(){
        return threadLocalInstanceThreadLocal.get();
    }

}

源碼用處

  • jdk1.8 的 Runtime類(餓漢式):private static Runtime currentRuntime = new Runtime();
  • jdk1.8 的 Desktop(容器式)
  • spring 中的 bean 的作用域 singleton
  • mybatis 中的 ErrorContext 類 (用到了ThreadLocal類型的單例模式)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章