單例模式(下) - 聊一聊單例模式的幾種寫法

在上一篇文章 單例模式(上)---如何優雅地保證線程安全問題中,咱們採起了懶漢式寫法來寫咱們的單例模式,而且重點講解了懶漢式中線程安全的問題。這篇咱們來說講單例模式中的其餘幾種寫法。java

上篇文章中,方法和變量的聲明都忘了加上「static」的聲明,這裏提醒一下。安全

懶漢式

懶漢式在上節咱們已經講過了,直接給出代碼:bash

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

複製代碼

懶漢式這種方式須要咱們來本身加鎖,保證線程安全的問題。ui

不過就算咱們保證了線程安全,這種寫法仍是沒法保證存在惟一一個對象實例。由於別人仍是能夠經過反射的方式來建立一個新的對象。我寫個示例:spa

public class Singleton {
    public static void main(String[] args) throws Exception{
        //得到構造器
        Constructor<Singleton> c = Singleton.class.getDeclaredConstructor();
        //把構造器設置爲可訪問
        c.setAccessible(true);
        //建立兩個實例對象
        Singleton s1 = c.newInstance();
        Singleton s2 = c.newInstance();
        //比較下兩個實例是否相等
        System.out.println(s1 == s2);
    }
}

複製代碼

打印結果:false。線程

因此懶漢式這種方式仍是存在一些缺點的。code

餓漢式

所謂餓漢式,就是一開始把對象實例建立出來,而不是等getInstance這個方法被調用纔來建立對象。代碼以下:對象

public class Singleton2 {
    private static Singleton2 instance = new Singleton2();
    //私有構造器
    private Singleton2(){};

    public static Singleton2 getInstance() {
        return instance;
    }
}
複製代碼

餓漢式與懶漢式相比,咱們不用管線程安全的問題,代碼看起來也比較簡潔。資源

可是,因爲對象一開始就被建立出來了,假如咱們從頭至尾都不調用getInstance()這個方法,那麼這個對象就白建立了。get

固然,和懶漢式同樣,餓漢式也存在反射問題。

總結一下餓漢式的一些問題:

一、有可能出現對象白白浪費的狀況。

二、和懶漢式同樣,沒法組織反射問題。

採用靜態內部類的寫法

直接上代碼

public class Singleton3 {
    //靜態內部類
    private static class LazyHolder{
        private static Singleton3 instance = new Singleton3(); 
    }
    //私有構造器
    private Singleton3(){};
    public static Singleton3 getInstance() {
        return LazyHolder.instance;
    }
}
複製代碼

因爲外部類沒法訪問靜態內部類,所以只有當外部類調用Singleton.getInstance()方法的時候,才能獲得instance實例。

而且,instance實例對象初始化的時機並非在Singleton被加載的時候,而是當getInstance()方法被調用的時候,靜態內部類纔會被加載,這時instance對象纔會被初始化。而且也是線程安全的。

因此,與餓漢式相比,經過靜態內部類的方式,能夠保證instance實例對象不會被白白浪費。

可是,它仍然存在反射問題。

採起枚舉的方式

直接上代碼:

public enum Singleton4 {
    //通常用大寫的了,不過爲了和前面的統一
    //我就用小寫的了
    
    instance;
}
複製代碼

枚舉的方式簡單吧?一行代碼就搞定了,不過和餓漢式同樣,因爲一開始instance實例就被建立了,因此有可能出現白白浪費的狀況。

可是,經過枚舉的方式,不只代碼簡單,線程安全,並且JVM還能阻止反射獲取枚舉類的私有構造器。

下面作個實驗

public enum Singleton4 {
    //通常用大寫的了,不過爲了和前面的統一
    //我就用小寫的了
    instance;

    public static void main(String[] args) throws Exception{
        //得到構造器
        Constructor<Singleton4> c = Singleton4.class.getDeclaredConstructor();
        //把構造器設置爲可訪問
        c.setAccessible(true);
        //建立兩個實例對象
        Singleton4 s1 = c.newInstance();
        Singleton4 s2 = c.newInstance();
        //比較下兩個實例是否相等
        System.out.println(s1 == s2);
    }
}
複製代碼

結果出現了異常:

Exception in thread "main" java.lang.NoSuchMethodException: singleton.Singleton4.()

at java.lang.Class.getConstructor0(Class.java:3082)

at java.lang.Class.getDeclaredConstructor(Class.java:2178)

at singleton.Singleton4.main(Singleton4.java:12)
複製代碼

因此,這種枚舉的方式能夠說的用的最多的一種方式了,惟一的缺點就是對象一開始就被建立,可能出現白白浪費沒有用到對象的狀況。

不過,整體上,仍是推薦採用枚舉的方式來寫。

獲取更多原創文章,能夠關注下個人公衆號:苦逼的碼農,我會不按期分享一些資源和軟件等。同時也感謝把文章介紹給更多須要的人。

相關文章
相關標籤/搜索