Singleton Pattern單例模式總結

在我們平時工作 或者 面試的時候,單例模式算是出現最頻繁的一種設計模式了,本文整理了單例模式的各種實現

1、餓漢模式【簡而言之,就是不管你用不用,我先創建出來】

public class HungrySingleton {

    private static final HungrySingleton hungry = new HungrySingleton();

    private HungrySingleton() {
    }

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

還有另一種寫法,利用靜態代碼塊的機制來實現

public class HungryStaticSingleton {

    private static final HungryStaticSingleton hungry;

    static {
        hungry = new HungryStaticSingleton();
    }

    private HungryStaticSingleton() {
    }

    public static HungryStaticSingleton getInstance() {
        return hungry;
    }

}

優點:沒有鎖,執行效率比較高,用戶體驗比懶漢模式要好

缺點:浪費內存【用不用都會先創建】

 

2、懶漢模式【用的時候再創建實例】

public class LazySimpleSingleton {

    public LazySimpleSingleton() {}

    private static LazySimpleSingleton lazy = null;

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

上面的懶漢模式是我們最常見的,但再多線程環境下可能創建多個實例,有興趣的可以自己試一下。

解決多線程環境下的安全隱患,最先想到的就是加鎖,如下所示:

public class LazySimpleSingleton {

    public LazySimpleSingleton() {}

    private static LazySimpleSingleton lazy = null;

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

這種方式雖然解決了多線程的安全問題,但是當線程比較多的時候,大批線程會阻塞,CUP的壓力也會上升。

那麼我們怎麼能解決這種情況呢,可以使用雙重檢查鎖的方式來解決,如下所示:

public class LazyDoubleCheckSingleton {

    private volatile static LazyDoubleCheckSingleton lazy = null;

    private LazyDoubleCheckSingleton() {}

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

以上情況會有很大的改觀,但用到鎖,終歸還是有一定的影響。有什麼更好的方法呢?可以考慮採用靜態內部類的方式來解決。

 

3、靜態內部類

public class LazyInnerClassSingleton {

    private LazyInnerClassSingleton() {}

    // 每一個關鍵字都不是多餘的,static 是爲了使單例的空間共享,保證這個方法不會被重寫、重載
    public static final LazyInnerClassSingleton getInstance() {
        // 在返回結果之前一定會加載內部類
        return lazyHolder.LAZY;
    }

    // 靜態內部類 默認不加載
    private static class lazyHolder {
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

這種方法可以很好的兼顧餓漢單例模式的內存浪費問題和 synchronized 的性能問題,完美的屏蔽了這兩個缺點。

 

4、反射破壞單例

上面的形式在大部分情況下都是沒有問題的,但是如果使用反射來創建對象的,我們發現仍然是可以創建兩個實例

public class Test {

    public static void main(String[] args) {

        // 利用反射則會破壞內部類單例模式
        try {
            Class<?> clazz = LazyInnerClassSingleton.class;
            Constructor c = clazz.getDeclaredConstructor(null);
            c.setAccessible(true);
            Object o1 = c.newInstance();
            Object o2 = c.newInstance();
            System.out.println(o1 == o2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

返回結果

false

顯然,創建了兩個不同的實例,我們可以在構造函數中加一些限制,一旦重複創建就拋出異常或者指定的信息。如下所示:

public class LazyInnerClassSingleton {

    private LazyInnerClassSingleton() {
        // 此構造函數中做限制,保證反射不會被反射破壞【使用序列化會創建多個實例】
        if (lazyHolder.LAZY != null) {
            throw new RuntimeException("不允許創建多個實例");
        }
    }

    // 每一個關鍵字都不是多餘的,static 是爲了使單例的空間共享,保證這個方法不會被重寫、重載
    public static final LazyInnerClassSingleton getInstance() {
        // 在返回結果之前一定會加載內部類
        return lazyHolder.LAZY;
    }

    // 靜態內部類 默認不加載
    private static class lazyHolder {
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

同樣使用上方的多線程進行測試,運行代碼,控制檯結果如下:

java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at com.xiang.designPattern.singleton.Test.main(Test.java:26)
Caused by: java.lang.RuntimeException: 不允許創建多個實例
	at com.xiang.designPattern.singleton.LazyInnerClassSingleton.<init>(LazyInnerClassSingleton.java:14)
	... 5 more

自此,上書所寫的單例模式基本可以滿足所有的情況,謹以此文記之。

如有更好的實現方式,還請不吝賜教!!!

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章