設計模式(四)Singleton設計模式

thekingisalwayslucky發表於2019-04-19

Singleton單例模式是最簡單的設計模式,它的主要作用是保證在程式執行生命週期中,使用了單例模式的類只能有一個例項物件存在。單例模式實現了類似C語言中全域性變數的功能,單例模式常用於註冊/查詢的服務。

單例模式的UML圖如下:

設計模式(四)Singleton設計模式
單例模式有兩種實現方式:飽漢模式和餓漢模式,如下:

1.飽漢單例模式例子程式碼:

public class Singleton1{

    //飽漢模式,宣告時就建立例項物件
    public static final Singleton1 instance = new Singleton1();

    //單類模式的構造方法必須為private,以避免通過構造方法建立物件例項,
    //並且必須顯示宣告構造方法,以防止使用預設構造方法
    private Singleton1(){}

    //單類模式必須對外提供獲取例項物件的方法
    public static Singleton1 geInstance(){
        return instance;
    }

}
2.餓漢單例模式即延遲初始化單例方式,例子程式碼:


public class Singleton2{

    //餓漢模式,宣告時不建立例項物件
    public static Singleton2 instance;

    //單類模式的構造方法必須為private,以避免通過構造方法建立物件例項,
    //並且必須顯示宣告構造方法,以防止使用預設構造方法
    private Singleton2(){}

    //單類模式必須對外提供獲取例項物件的方法,延遲初始化的單類模式必須使用synchronized同步關鍵字,否則多執行緒情況下很容易產生多個例項物件
    public static synchronized Singleton2 geInstance(){
        //延遲初始化,只有當第一次使用時才建立物件例項
        if(instance == null){
            instance = new Singleton2();
        }
        return instance;
    }
}

一般認為飽漢模式要比餓漢模式更加安全。

上面兩種Singleton單例設計模式的實現方式都隱藏有如下的問題:

(1).雖然構造方式的訪問修飾符為private,即除了自身以外其他任何類都無法呼叫,但是通過反射機制的setAccessiable(true)方法可以訪問私有方法和屬性。因此Singleton單例模式必須考慮這種例外情況。

(2).物件序列化之後再反序列化時會生成新的物件,因此當Singleton單例模式類實現序列化介面時,必須顯式宣告所有的欄位為tranisent,並且提供如下的readResolve方法來防止通過序列化破壞單態模式:



private Object readResolve(){
    return INSTANCE;
}

3.使用Lazy initialization holder class模式實現單態:


public class Singleton3 {  

    /** 
     * 類級的內部類,也就是靜態的成員式內部類,該內部類的例項與外部類的例項 
     * 沒有繫結關係,而且只有被呼叫到才會裝載,從而實現了延遲載入 
     */  
    private static class SingletonHolder{   
        //靜態初始化器,由JVM來保證執行緒安全 
        private static Singleton3 instance = new Singleton3();  
    }  

    //私有化構造方法  
    private Singleton3(){  
    }  

    public static  Singleton3 getInstance(){  
        return SingletonHolder.instance;  
    }  
}


當getInstance方法第一次被呼叫的時候,它第一次讀取SingletonHolder.instance,導致SingletonHolder類得到初始化;而這個類在裝載並被初始化的時候,會初始化它的靜態域,從而建立Singleton的例項,由於是靜態的域,因此只會被虛擬機器在裝載類的時候初始化一次,並由虛擬機器來保證它的執行緒安全性。 
這個模式的優勢在於,getInstance方法並沒有被同步,並且只是執行一個域的訪問,因此延遲初始化並沒有增加任何訪問成本。 

4.在JDK1.5之後引入了Enum列舉,因此在JDK1.5之後Singleton單例模式又有了第三種實現方式,也是最好的實現方式,例子如下:



public enum Singleton4{
    INSTANCE{
        public void doSomething(){
            ...
        }
    };
    public abstract void doSomething();  
}

Singleton單例模式中只有一個INSTANCE列舉元素,列舉可以保證真個程式生命週期中只有一個例項物件存在,同時還避免了常規Singleton單例模式private構造方法被反射呼叫和序列化問題(列舉提供了序列化保證機制,確保多次序列化和反序列化不會建立多個例項物件)。
注意:java中除了構造方法可以建立物件例項以外,還可以通過克隆方法(clone()是Object中的protected方法)來建立物件,若單例物件直接繼承自Object物件,則如果沒有提供具體clone方法實現,則當呼叫克隆方法建立物件時,會丟擲執行時的異常CloneNotSupportedException。

若單例類繼承了實現克隆方法的類,則在單例類中必須覆蓋父類的克隆方法,顯式丟擲異常CloneNotSupportedException。

另外,實現了單例模式的類不能再有派生子類,因為構造方式是私有的,子類無法呼叫父類構造方法,因此達到了Final的效果。

JDK的中單例模式的應用:
java.lang.Runtime

單例模式的優點:
* 在記憶體中只有一個物件,節省記憶體空間。

* 避免頻繁的建立銷燬物件,可以提高效能。

* 避免對共享資源的多重佔用。

* 可以全域性訪問。

適用場景:由於單例模式的以上優點,所以是程式設計中用的比較多的一種設計模式。我總結了一下我所知道的適合使用單例模式的場景:

* 需要頻繁例項化然後銷燬的物件。

* 建立物件時耗時過多或者耗資源過多,但又經常用到的物件。

* 有狀態的工具類物件。

* 頻繁訪問資料庫或檔案的物件。

* 以及其他我沒用過的所有要求只有一個物件的場景。

單例模式注意事項:

* 只能使用單例類提供的方法得到單例物件,不要使用反射,否則將會例項化一個新物件。

* 不要做斷開單例類物件與類中靜態引用的危險操作。

* 多執行緒使用單例使用共享資源時,注意執行緒安全問題。

關於java中單例模式的一些爭議:

單例模式的物件長時間不用會被jvm垃圾收集器收集嗎?

看到不少資料中說:如果一個單例物件在記憶體中長久不用,會被jvm認為是一個垃圾,在執行垃圾收集的時候會被清理掉。對此這個說法,我持懷疑態度我的觀點是:在hotspot虛擬機器1.6版本中,除非人為地斷開單例中靜態引用到單例物件的聯接,否則jvm垃圾收集器是不會回收單例物件的。


在一個jvm中會出現多個單例嗎?

在分散式系統、多個類載入器、以及序列化的的情況下,會產生多個單例,這一點是無庸置疑的。那麼在同一個jvm中,會不會產生單例呢?使用單例提供的getInstance()方法只能得到同一個單例,除非是使用反射方式,將會得到新的單例。程式碼如下



複製程式碼
Class c = Class . forName ( Singleton . class . getName ( ) ) ;
Constructor ct = c . getDeclaredConstructor ( ) ;
ct . setAccessible ( true ) ;
Singleton singleton = ( Singleton ) ct . newInstance ( ) ;
複製程式碼

note:免費的才是最貴的

相關文章