設計模式的學習總結-單例模式詳解

一、單例模式的幾種創建方式

1.餓漢式單例。 特點:線程安全,使用效率高,但是不能延遲加載

2.懶漢式單例。特點:線程安全,調用效率不高,但是能延遲加載

3.Double CheckLock實現單例(懶漢式)。特點:DCL也就是雙重鎖判斷機制,基於JVM底層模型

4.靜態內部類。特點:線程安全,調用效率高,可以延時加載

5.枚舉式單例。特點:線程安全,調用效率高,可以天然的防止反射和反序列化調用

二、單例模式詳解

1.餓漢式單例

/**
 * 餓漢式單例
 */
public class HungrySingle {

    //餓漢式:在類初始化的時候就分配內存,並創建對象
    private static HungrySingle hungrySingle = new HungrySingle();

    private HungrySingle(){
        //構造方法加判斷,防止反射攻擊
        if( hungrySingle != null){
            throw new RuntimeException("單例構造器禁止反射調用");
        }
    }

    public static HungrySingle getInstance(){
        return hungrySingle;
    }
}

    public static void main(String[] args){

        try {
            //調用者裝逼,通過反射獲取對象,破壞單例
            Class clazz = HungrySingle.class;
            Constructor c = clazz.getDeclaredConstructor();
            c.setAccessible(true);

            HungrySingle h1 = (HungrySingle) c.newInstance();

            //常規獲取對象
            HungrySingle h2 = HungrySingle.getInstance();

            System.out.println("h1="+h1);
            System.out.println("h2="+h2);

            System.out.println(h1 == h2);

        }catch (Exception e){
            e.printStackTrace();
        }
    }

 2.懶漢式單例

/**
*懶漢式單例
*/
public class LazySingle {

    //懶漢式:在運行時才創建對象,懶加載
    private static LazySingle lazySingle = null;

    private LazySingle(){
        //構造方法加判斷,防止反射攻擊
        if (lazySingle != null){
            throw new RuntimeException("單例構造器禁止反射調用");
        }
    }

    public synchronized static LazySingle getInstance(){ //鎖住整個類,效率底
        if (lazySingle == null) {
                lazySingle = new LazySingle();
        }
        return lazySingle;
    }
}

3.Double CheckLock實現單例(懶漢式)

public class LazySingle {

    private static LazySingle lazySingle = null;

    private LazySingle(){
        //構造方法加判斷,防止反射攻擊
        if (lazySingle != null){
            throw new RuntimeException("單例構造器禁止反射調用");
        }
    }

    public  static LazySingle getInstance(){
        //雙重鎖判斷
        if (lazySingle == null) {

            synchronized (LazySingle.class) {

                if (lazySingle == null) {
                    lazySingle = new LazySingle();
                }
            }
        }
        return lazySingle;
    }
    //在反序列化時,用這個方法返回的對象引用替代readObject新建的引用
    private Object readResolve() {
        return lazySingle;
    }
}

4.靜態內部類

public class InsideSingle implements Serializable {

    //volatile屬性,是類加載順序可見
    private volatile static InsideSingle insideSingle = null;

    private InsideSingle(){
        //構造方法加判斷,防止反射攻擊
        if (insideSingle != null){
            throw new RuntimeException("單例構造器禁止反射調用");
        }
    }
    /**
     * 靜態內部類創建對象
     */
    private static class getInside{
        private static InsideSingle insideSingle = new InsideSingle();
    }
    public static InsideSingle getInstance(){
        return getInside.insideSingle;
    }
    //在反序列化時,用這個方法返回的對象引用替代readObject新建的引用
    private Object readResolve() {
        return getInstance();
    }
}

 

 靜態內部類的優點:外部類加載時並不需要立即加載內部類,內部類不被加載則不去初始化insideSingle,故而不佔內存。即當InsideSingle第一次被加載時,並不需要去加載getInside,只有當getInstance()方法第一次被調用時,纔會去初始化insideSingle,第一次調用getInstance()方法會導致虛擬機加載getInside類,這種方法不僅能確保線程安全,也能保證單例的唯一性,同時也延遲了單例的實例化。

這裏:如何實現線程安全的?首先,我們先了解下類的加載機制。詳細解釋自行去了解吧

1.在內存中開闢一個空間。

2.創建一個對象的地址。

3.把創建的地址的引用指向所開闢內存的空間。

4.對象創建並賦值。

有時候第二步和第三步的順序並不確定,所以我們可以加上 volatile 保證類加載順序的可見性。

5.枚舉式單例

//常量中去使用,常量不就是用來大家都能夠共用嗎?
//通常在通用API中使用
public enum EnumSingle {
    INSTANCE;
    private Object data;
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
    public static EnumSingle getInstance(){
        return INSTANCE;
    }
}

枚舉單例在jvm內部就有效的防止了反射破壞情況。同時序列化也沒有多創建對象。

Spring中的做法,就是用這種註冊式單例

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