單例模式的7種創建方式


1.餓漢式 

public final class SingletonObject1 {
    private static final SingletonObject1 instance = new SingletonObject1();

    private SingletonObject1() {
    } 

    public static SingletonObject1 getInstance() {
        return instance;
    }
}

  餓漢式的創建方法關鍵在於 instance作為類變量直接得到了初始化,這種方法的優點在於多線程環境下能夠百分百地保證同步,在多線程環境下不可能被實例化兩次,但是instance若是被加載后很長一段時間后才使用,就意味着instance實例開辟的堆內存會駐留更長的時間,所以更優的創建方式應是伴隨着懶加載的。

 

2.懶漢式

public final class SingletonObject2 {
    
    private static  SingletonObject2 instance == null;

    private SingletonObject2() {
    }

    public static SingletonObject2 getInstance() {
        if (null == instance)
            instance = new SingletonObject2();
        return instance;
    }
}

  所謂懶漢式就是在使用的時候才去創建,這樣就可以避免類在初始化時提前創建,但是這種方式有一個很大的缺點,在多線程的環境下,若一開始因為線程上下文切換的原因,兩個線程都通過了null==instance的if循環,這樣就是new出兩個實例,無法保證單例的唯一性,所以有下面第三種方法。

 

3.懶漢式+同步方法

public final class SingletonObject3 {
   
    private static SingletonObject3 instance ;

    private SingletonObject3() {
    }

    public synchronized static SingletonObject3 getInstance() {
        if (null == instance)
            instance = new SingletonObject3();
        return instance;
    }
}

  這種方法通過添加同步控制既滿足了懶加載,又滿足了instance實例的唯一性,但是,添加了同步控制后getInstance方法的調用是串行化的,效率較低,因此引出第四種創建方式---Double Check方式

 

4.Double-Check

public final class SingletonObject4 {

Socket socket; //模仿一些資源的實例化

private static SingletonObject4 instance ;

private SingletonObject4() {
this.socket = new Socket();
}

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

  若有兩個線程通過了第一個Check循環,進入第二個Check循環是串行化的,只能有一個線程進入,這樣當這個線程創建完成后,另外的線程就無法通過第二個循環了,保證了實例的唯一性,隨后的線程也不會通過第一個Check循環,也就不會有同步控制的環節了。但是,這種方法也伴隨着一個缺點,它可能會引起空指針的異常。

  假設這個單例創建有一些其他的資源,例如Socket、Connection,這些資源在構造函數中也會被實例化,那樣在創建單例的時候,就是要實例化自身還有Socket這些資源,那根據JVM的重排序和Happens-before原則,有可能會出現先實例化自身,再去實例化Socket這些資源,若在此時只實例化了自己的情況下,別的線程調用了這個單例中Socket這些資源的方法,而此時它們可能還沒有被實例化,這樣就會拋出空指針的異常,在此引出第五種創建方法----Volatile+Double-Check

 

5.Volatile+Double-Check

public final class SingletonObject5 {
  
    private volatile static SingletonObject5 instance ;

    private SingletonObject5() {
    } 

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

  volatile關鍵字可以防止重排序的發生,在此不對volatile作詳細介紹,通過volatile關鍵字,這種模式可以說是滿足懶加載、多線程下單例的唯一性、安全性的。

 

6.Holder方式

public final class SingletonObject6{

    private SingletonObject6() {
    }

    private static class InstanceHolder {
        private static  SingletonObject6 instance = new SingletonObject6();
    }

    public static SingletonObject6 getInstance() {
        return InstanceHolder.instance;
    }
   
}

  Holder這種方式是本人最喜歡的一種創建方式,它借助了類加載的特點,在SingletonObject6中並沒有instance的靜態成員,而是放置了靜態內部類InstanceHolder之中,因此SingletonObject6的初始化過程中並不會實例化instance,當Holder被主動引用的時候才會進行實例化,而在instance被實例化時是會在Java程序編譯器中收集至<cliinit>()方法中的,該方法是同步方法,且保證內存的可見性、原子性和順序性,可以說是餓漢方式的優化版,這種創建方式是最為廣泛的方式之一。

 

7.枚舉法

  

public enum  EnumSingleton {
    Instance;
    public void method(){
        
    }
}

  枚舉法是《Effective Java》中作者推薦的方式 ,這種方法極為簡單,因為枚舉類型本身是final的,不允許被繼承,且同樣是線程安全的,且只能被實例化一次和不用考慮序列化之類的問題,使用的時候可以直接EnumSingleton.Instance.method()就可以使用了,但是它不能實現懶加載,比如調用其中的靜態方法也是會實例化Instance的,讀者可以自行測試,但是也可以進行改造,讓枚舉充當Holder的角色增加懶加載的特性,代碼如下

public final class SingletonObject7 {
    
    private SingletonObject7() {}

    private enum Singleton {
        INSTANCE;

        private final SingletonObject7 instance;

        Singleton(){
            instance = new SingletonObject7();
        }

        public SingletonObject7 getInstance() {
            return instance;
        }
    }

    public static SingletonObject7 getInstance() {
        return Singleton.INSTANCE.getInstance();
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM