【Java】設計模型-五種單例模型

一. 什麼是單例模式

只須要某個類同時保留一個對象,不但願有更多對象,此時,咱們則應考慮單例模式的設計。java

單例模式的主要做用是保證在Java程序中,某個類只有一個實例存在。安全

單例模式有不少好處,它可以避免實例對象的重複建立,不只能夠減小每次建立對象的時間開銷,還能夠節約內存空間;session

可以避免因爲操做多個實例致使的邏輯錯誤。若是一個對象有可能貫穿整個應用程序,並且起到了全局統一管理控制的做用,那麼單例模式也許是一個值得考慮的選擇。多線程

二. 單例模式的特色

1. 單例模式只能有一個實例。併發

2. 單例類必須建立本身的惟一實例。ide

3. 單例類必須向其餘對象提供這一實例。函數

三. 單例模式VS靜態類

在知道了什麼是單例模式後,我想你必定會想到靜態類,「既然只使用一個對象,爲什麼不乾脆使用靜態類?」,這裏我會將單例模式和靜態類進行一個比較。測試

1. 單例能夠繼承和被繼承,方法能夠被override,而靜態方法不能夠。spa

2. 靜態方法中產生的對象會在執行後被釋放,進而被GC清理,不會一直存在於內存中。線程

3. 靜態類會在第一次運行時初始化,單例模式能夠有其餘的選擇,便可以延遲加載。

4. 基於2, 3條,因爲單例對象每每存在於DAO層(例如sessionFactory),若是反覆的初始化和釋放,則會佔用不少資源,而使用單例模式將其常駐於內存能夠更加節約資源。

5. 靜態方法有更高的訪問效率。

6. 單例模式很容易被測試。

幾個關於靜態類的誤解:

誤解一:靜態方法常駐內存而實例方法不是。

實際上,特殊編寫的實例方法能夠常駐內存,而靜態方法須要不斷初始化和釋放。

誤解二:靜態方法在堆(heap)上,實例方法在棧(stack)上。

實際上,都是加載到特殊的不可寫的代碼內存區域中。

靜態類和單例模式情景的選擇:

情景一:不須要維持任何狀態,僅僅用於全局訪問,此時更適合使用靜態類。

情景二:須要維持一些特定的狀態,此時更適合使用單例模式。

單例模型的寫法

一、餓漢模式

public class Singleton{
    private static Singleton instance = new Singleton();
    private Singleton(){}
    public static Singleton newInstance(){
        return instance;
    }
}

註解:初試化靜態的instance建立一次。若是咱們在Singleton類裏面寫一個靜態的方法不須要建立實例,它仍然會早早的建立一次實例。而下降內存的使用率。

缺點:沒有lazy loading的效果,從而下降內存的使用率。

二、懶漢模式

public class Singleton{
    private static Singleton instance = null;
    private Singleton(){}
    public static Singleton newInstance(){
        if(null == instance){
            instance = new Singleton();
        }
        return instance;
    }
}

註解:Singleton的靜態屬性instance中,只有instance爲null的時候才建立一個實例,構造函數私有,確保每次都只建立一個,避免重複建立。
缺點:只在單線程的狀況下正常運行,在多線程的狀況下,就會出問題。例如:當兩個線程同時運行到判斷instance是否爲空的if語句,而且instance確實沒有建立好時,那麼兩個線程都會建立一個實例,所以須要加鎖解決線程同步問題,實現以下:

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

三、雙重校驗鎖 

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

註解:只有當instance爲null時,須要獲取同步鎖,建立一次實例。當實例被建立,則無需試圖加鎖。
缺點:用雙重if判斷,複雜,容易出錯。

四、靜態內部類

public class Singleton{
    private static class SingletonHolder{
        public static Singleton instance = new Singleton();
    }
    private Singleton(){}
    public static Singleton newInstance(){
        return SingletonHolder.instance;
    }
}

這種方式一樣利用了類加載機制來保證只建立一個instance實例。它與餓漢模式同樣,也是利用了類加載機制,所以不存在多線程併發的問題。不同的是,它是在內部類裏面去建立對象實例。這樣的話,只要應用中不使用內部類,JVM就不會去加載這個單例類,也就不會建立單例對象,從而實現懶漢式的延遲加載。也就是說這種方式能夠同時保證延遲加載和線程安全。

五、枚舉

public enum Singleton{
    instance;
    public void whateverMethod(){}    
}  

上面提到的四種實現單例的方式都有共同的缺點:

1)須要額外的工做來實現序列化,不然每次反序列化一個序列化的對象時都會建立一個新的實例。

2)可使用反射強行調用私有構造器(若是要避免這種狀況,能夠修改構造器,讓它在建立第二個實例的時候拋異常)。

       而枚舉類很好的解決了這兩個問題,使用枚舉除了線程安全和防止反射調用構造器以外,還提供了自動序列化機制,防止反序列化的時候建立新的對象。所以,《Effective Java》做者推薦使用的方法。不過,在實際工做中,不多看見有人這麼寫。

總結

       本文總結了五種Java中實現單例的方法,其中前兩種都不夠完美,雙重校驗鎖和靜態內部類的方式能夠解決大部分問題,平時工做中使用的最多的也是這兩種方式。枚舉方式雖然很完美的解決了各類問題,可是這種寫法多少讓人感受有些生疏。我的的建議是,在沒有特殊需求的狀況下,使用第三種和第四種方式實現單例模式。

相關文章
相關標籤/搜索