設計模式——單例模式

設計模式——單例模式

單例模式:如果程序運行過程中,某個類的實例只有唯一的一份,那麼可以說這個類就是單例的,相應的設計模式稱謂單例模式。這種模式涉及到一個單一的類,該類自己負責創建自己的實例,同時必須確保這運行過程中只能有一個實例被創建。

單例模式的實現方式有:餓漢式,懶漢式,雙重校驗式,靜態內部類方式,枚舉方式,下面具體每一種方式的實現代碼並分析優劣。

1-餓漢式

/**
 * 單例模式:餓漢式,天然線程安全,在初始化時立即加載這個對象
 */
public class Singleton1 {
    //0-私有化構造器
    private Singleton1(){}


    //1-類初始化時立即加載對象
    private static Singleton1 singleton = new Singleton1();

    //2-靜態方法提供單例對象
    public static Singleton1 getSingleton(){
        return singleton;
    }
}

餓漢式:天然線程安全,沒有加鎖,效率較高,在類加載的時候就初始化實例,可能會產生垃圾對象,沒有達到懶加載的效果。

測試代碼:

@Test
public void testSingleton1(){
    Singleton1 singleton1 = Singleton1.getSingleton();
    Singleton1 singleton2 = Singleton1.getSingleton();

//斷言兩者是同一對象
    Assert.assertTrue(singleton1 == singleton2);
}

 

2-懶漢式

/**
 * 懶漢式,在需要使用對象時在new出來,有線程安全問題
 * 資源利用率提高了,但是每次調用的時候都要同步,併發效率低了
 */
public class Singleton2 {
    //0-私有化構造器
    private Singleton2(){
        //多次調用直接拋出異常,確保單例
        if(null != singleton){
            throw new RuntimeException("單例不允許創建多個對象");
        }
    }

    //1-設置私有靜態屬性
    private static Singleton2 singleton;

    //2-提供獲取實例方法,加synchronized關鍵字則可以保證線程安全
    public static synchronized Singleton2 getSingleton(){
        if(singleton == null){
            singleton = new Singleton2();
        }
        return singleton;
    }
}

 

懶漢式:需要加鎖進行同步才能保證線程安全,效率較低,實現了懶加載,在調用的時候纔會校驗是否需要加載,如果已經加載了則直接返回實例對象,否則調用構造器方法進行初始化並返回。

測試代碼:

@Test
public void testSingleton2(){
    Singleton2 singleton1 = Singleton2.getSingleton();
    Singleton2 singleton2 = Singleton2.getSingleton();
    //斷言兩者是同一對象
    Assert.assertTrue(singleton1 == singleton2);
}

 

3-雙重校驗方式

/**
 * 雙重檢驗鎖方式:在靜態獲取單例實例時做了兩次判斷,且採用synchronized關鍵字確保單例
 */
public class Singleton3 {
    //0-私有化構造器
    private Singleton3(){}

    //1-設置私有靜態屬性,volatile關鍵字保證數據在多個線程間修改是可見的
    private volatile static Singleton3 singleton;

    //2-提供獲取實例方法,雙重檢驗,加synchronized關鍵字保證線程安全
    public static Singleton3 getSingleton(){
        if(null == singleton){
            synchronized (Singleton3.class){
                if(null == singleton){
                    singleton = new Singleton3();
                }
            }
        }
        return singleton;
    }
}

雙重檢驗方式:採用雙鎖機制,在多線程下也能有較好的性能。

測試代碼:

@Test
public void testSingleton3(){
    Singleton3 singleton1 = Singleton3.getSingleton();
    Singleton3 singleton2 = Singleton3.getSingleton();
    //斷言兩者是同一對象
    Assert.assertTrue(singleton1 == singleton2);
}

 

Spring中也是採用這種方式確保bean在容器中的唯一性(必須在<bean>中沒有定義scope的值或者scope屬性爲singleton)代碼如下所示:

DefaultSingletonBeanRegistry.class

 

protected Object getSingleton(String beanName, boolean allowEarlyReference) {

    Object singletonObject = this.singletonObjects.get(beanName);

    if(singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {

        Map var4 = this.singletonObjects;

        synchronized(this.singletonObjects) {

            singletonObject = this.earlySingletonObjects.get(beanName);

            if(singletonObject == null && allowEarlyReference) {

                ObjectFactory singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);

                if(singletonFactory != null) {

                    singletonObject = singletonFactory.getObject();

                    this.earlySingletonObjects.put(beanName, singletonObject);

                    this.singletonFactories.remove(beanName);

                }

            }

        }

    }

    return singletonObject != NULL_OBJECT?singletonObject:null;

}

4-靜態內部類方式

/**
 * 靜態內部類實現方式:
 * 1、外部類沒有static屬性,不會像餓漢式那樣立即加載對象
 * 2、只有真正調用getSingleton(),纔會加載靜態內部類,加載類時是線程安全的
 *    只能被賦值一次
 * 3、兼備了併發高效調用和延遲加載的優勢
 */
public class Singleton4 {
    //0-私有化構造器
    private Singleton4() {
    }

    //1-定義一個靜態內部類,持有一個單例實例對象
    private static final class Singleton4Holder {
        private static final Singleton4 singleton = new Singleton4();
    }

    //2-獲取單例實例方法
    public static Singleton4 getSingleton() {
        return Singleton4Holder.singleton;
    }
}

靜態內部類方式:利用了靜態域延遲初始化,餓漢式是在類加載的時候就初始化實例,但是靜態內部類的方式必須在Singleton4Holder被主動使用的時候纔會加載實例,線程安全,只適用於靜態域的情況。

測試代碼:

@Test
public void testSingleton4(){
    Singleton4 singleton1 = Singleton4.getSingleton();
    Singleton4 singleton2 = Singleton4.getSingleton();
    //斷言兩者是同一對象
    Assert.assertTrue(singleton1 == singleton2);
}

 

5-枚舉方式

/**
 * 枚舉方式:
 */
public enum Singleton5 {
    INSTANCE;
}

測試代碼:

@Test
public void testSingleton5(){
    Singleton5 singleton1 = Singleton5.INSTANCE;
    Singleton5 singleton2 = Singleton5.INSTANCE;
    //斷言兩者是同一對象
    Assert.assertTrue(singleton1 == singleton2);
}

枚舉方式:實現單例的最佳方式,自動支持序列化機制,絕對防止多次實例化對象,而且可以防止反序列化重新創建對象,從JDK1.5之後纔有enum特性。

發佈了31 篇原創文章 · 獲贊 41 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章