java設計模式1——單例模式

java設計模式1——單例模式

一、單例模式介紹

1.一、核心做用:保證一個類只有一個實例,而且提供一個訪問該實例的全局訪問點

1.二、常見場景

1.三、單例模式的優勢

1.四、常見的五種單例模式實現方式

二、餓漢式

2.一、第一步:私有化構造器。(防止外部直接new對象)

//保證類只有一個實例,私有其構造器
private SingletonDemo01() {

}

2.二、第二步:建立自身對象。

//建立自身對象
private static SingletonDemo01 instance = new SingletonDemo01();

2.三、第三步:提夠對外全局公開的方法

//全局公開的方法
public static SingletonDemo01 getInstance() {
    return instance;
}

2.四、測試是否爲單例

class SingletonDemo01Test {
    public static void main(String[] args) {
        SingletonDemo01 instance = SingletonDemo01.getInstance();
        SingletonDemo01 instance2 = SingletonDemo01.getInstance();

        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());
        System.out.println(instance == instance2);
    }
}

輸出的結果爲:java

356573597
356573597
true

2.五、弊端分析:

餓漢式一上來就會對對象進行建立,無論後續有沒有用到,若是對於較大內存的對象然後續也都沒有用到,則會形成較大的內存空間的浪費。

2.六、本類所有代碼

package com.xgp.company.第一種_單例模式.餓漢式;

/**
 *
 * 核心:保證一個類只有一個實例,而且提供一個範圍該實例的全局訪問點
 */
public class SingletonDemo01 {

    //保證類只有一個實例,私有其構造器
    private SingletonDemo01() {

    }

    //建立自身對象
    private static SingletonDemo01 instance = new SingletonDemo01();

    //全局公開的方法
    public static SingletonDemo01 getInstance() {
        return instance;
    }

}

class SingletonDemo01Test {
    public static void main(String[] args) {
        SingletonDemo01 instance = SingletonDemo01.getInstance();
        SingletonDemo01 instance2 = SingletonDemo01.getInstance();

        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());
        System.out.println(instance == instance2);
    }
}

三、懶漢式

目的:解決餓漢式可能存在的內存空間浪費的問題進行該進,不一上來就建立對象,而是在使用時再來建立對象。

3.一、懶漢式的代碼以下:

public class SingletonDemo02 {

    //保證類只有一個實例,私有其構造器
    private SingletonDemo02() {

    }
    //建立自身對象,當時不用當即加載
    private static SingletonDemo02 instance;

    //全局公開的方法  synchronized做用:加鎖  多線程進來時會不安全,效率較低
    public static synchronized SingletonDemo02 getInstance() {
        if(instance == null) {
            instance = new SingletonDemo02();
        }
        return instance;
    }

}

3.二、分析:代碼中爲什要使用synchronized關鍵字來進行上鎖

考率一下多線程的狀況下,若是沒有上鎖,兩個線程A、B一前之後的很緊密的執行該方法,而此時A完成了初始化操做,可是尚未進行返回,B此時進入判斷語句中,此時也爲null,這樣也會進行初始化操做,因而乎,就獲得了兩個對象了,違反了單例模式設計得原則。

3.三、弊端分析:

該方法使用了synchronized對一個返回得方法進行了上鎖,該方法得執行效率會較慢。

四、DCL_懶漢式

目的:DCL_懶漢式又稱爲雙重檢測懶漢式,爲了改進懶漢式效率不高的問題

4.一、該類的1版本的代碼以下:

public class SingletonDemo03 {

    //保證類只有一個實例,私有其構造器
    private SingletonDemo03() {
    }
    //建立自身對象,當時不用當即加載 volatile做用:盡大可能的解決極端狀況的問題
    private volatile static SingletonDemo03 instance;

    //全局公開的方法  synchronized做用:加鎖  多線程進來時會不安全,效率較低
    public static SingletonDemo03 getInstance() {
        if(instance == null) {
            //定一次進來時加鎖,後面進來時就不加鎖了,提升了效率
            synchronized (SingletonDemo03.class) {
                if(instance == null) {
                    instance = new SingletonDemo03();
                }
            }
        }
        return instance;
    }

}

4.二、分析1版本代碼:

一樣考率多線程的狀況下,A、B兩線程相繼的進入方法中,A率先得到初始化權力,進行上鎖,進行對對象的建立,而且由於有volatile關鍵字,可以快速的將對象更新給B。若是B未進入判斷語句中,則此時B中有該類對象了,直接返回了。若是B進入了判斷語句中,可是A已經上鎖了,也沒法進入了,只有返回了。

4.三、1版本的弊端

一、再考慮多線程的極端狀況,若是該類比較龐大,建立對象須要花費很長時間,B已經進入函數中了,而A建立對象的時間會比B走完該函數的時間長,則此時該函數將會返回B,而B=NULL。

二、該模式沒法防止反射

4.四、版本2代碼:

public class SingletonDemo03 {

    //破壞兩次都用反射建立對象
    private static boolean flag = false;

    //保證類只有一個實例,私有其構造器
    private SingletonDemo03() {
        //防治被反射
        synchronized (SingletonDemo03.class) {
            if(flag == false) {
                flag = true;
            }else {
                throw new RuntimeException("不要試圖用反射破壞單例");
            }
        }

    }
    //建立自身對象,當時不用當即加載 volatile做用:盡大可能的解決極端狀況的問題
    private volatile static SingletonDemo03 instance;

    //全局公開的方法  synchronized做用:加鎖  多線程進來時會不安全,效率較低
    public static SingletonDemo03 getInstance() {
        if(instance == null) {
            //定一次進來時加鎖,後面進來時就不加鎖了,提升了效率
            synchronized (SingletonDemo03.class) {
                if(instance == null) {
                    instance = new SingletonDemo03();
                }
            }
        }
        return instance;
    }

}

4.五、弊端分析

該版本一樣未解決上面的問題,只是加大了反射獲取對象的難度,反射破壞單例的代碼以下:

class SingletonDemo03Test {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        /*
        SingletonDemo03 instance1 = SingletonDemo03.getInstance();
        SingletonDemo03 instance2 = SingletonDemo03.getInstance();

        System.out.println(instance1 == instance2);
*/
        Class<SingletonDemo03> clazz = SingletonDemo03.class;

        //反射破壞單例
        Constructor<SingletonDemo03> declaredConstructor = clazz.getDeclaredConstructor(null);

        declaredConstructor.setAccessible(true);

        SingletonDemo03 instance1 = declaredConstructor.newInstance();

        //破壞flag
        Field flag = clazz.getDeclaredField("flag");
        flag.setAccessible(true);
        flag.set(clazz,false);
        System.out.println(flag.get(clazz));


        SingletonDemo03 instance2 = declaredConstructor.newInstance();

        System.out.println(instance1 == instance2);

        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

運行結果:設計模式

false
false
21685669
2133927002

五、靜態內部類實現

該方式可以不適用synchronized提升效率,而且可以保證在多線程的狀況下依舊是單例,代碼以下:

public class SingletonDemo04 {
    private SingletonDemo04() {

        //防治被反射
        synchronized (SingletonDemo04.class) {
            if(InnerClass.instance != null) {
                throw new RuntimeException("不要試圖用反射破壞單例");
            }
        }
    }

    private static class InnerClass {
        private static final SingletonDemo04 instance = new SingletonDemo04();
    }

    public static SingletonDemo04 getInstance() {
        return InnerClass.instance;
    }
}

六、利用枚舉來實現

java中最爲推薦的是使用枚舉類來建立單例對象,由於枚舉類有這純自然的優點,沒法被反射。點擊進反射建立對象的newInstance()方法的源碼中能夠發現:

@CallerSensitive
public T newInstance(Object ... initargs)
    throws InstantiationException, IllegalAccessException,
           IllegalArgumentException, InvocationTargetException
{
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, null, modifiers);
        }
    }
    if ((clazz.getModifiers() & Modifier.ENUM) != 0)
        throw new IllegalArgumentException("Cannot reflectively create enum objects");
    ConstructorAccessor ca = constructorAccessor;   // read volatile
    if (ca == null) {
        ca = acquireConstructorAccessor();
    }
    @SuppressWarnings("unchecked")
    T inst = (T) ca.newInstance(initargs);
    return inst;
}

此外,枚舉類也自己就是單例的,因此使用枚舉類來建立單例對象最爲適合,而現在大多數的框架的單例也都是經過這樣的方法進行建立的。代碼以下:

/**
 * 反射不能破壞枚舉類型,枚舉類純自然的單例,最簡單
 */
public enum SingletonDemo05 {
    INSTANCE;

    public SingletonDemo05 getInstance() {
        return INSTANCE;
    }

    public String hello() {
        return "Hello World!";
    }
}

class SingletonDemo05Test {
    public static void main(String[] args) {
        SingletonDemo05 instance1 = SingletonDemo05.INSTANCE;
        SingletonDemo05 instance2 = SingletonDemo05.INSTANCE.getInstance();

        System.out.println(instance1 == instance2);

        String hello = SingletonDemo05.INSTANCE.hello();
        System.out.println(hello);
    }
}
相關文章
相關標籤/搜索