設計模式學習01—單例模式

1、動機與定義

     系統中有些資源只能有一個,或者一個就夠,多個浪費。例如一個系統只能有一個窗口管理器或文件系統、一個系統只能有一個計時器或序號生成器、web系統只能有一個頁面計數器等等。此時,最好就需要把這些資源設置成有且僅有一個實例。
     代碼中也就是如何保證一個類只有一個實例並且這個實例能夠被訪問呢?只有一個實例的就意味着不能讓其他類來實例化,也就是隻能自己實例化自己。能夠被訪問也就意味着自身要對外提供全局方法來獲取到這個實例,這就是單例模式。
     單例模式定義:確保某一個類只有一個實例,而且自行實例化並且向整個系統提供這個實例。
     單例模式通常代表着系統具有唯一性的資源。主要有3點:只有一個實例;自行創建這個實例;自行向整個系統提供這個實例。

2、結構與類圖

     單例模式是創建型模式,其實結構非常簡單,需要注意以下3點:
     1、構造方法私有:不讓外部實例化,只能將構造函數私有;
     2、提供一個公共靜態方法獲取實例:獲取這個實例前是沒有實例的,只能用靜態的。
     3、實例保存到自身靜態私有屬性上:獲取方法是靜態的,實例當然也只能是靜態的,最好是final的,單例不允許修改;
     通用類圖如下:
     
     代碼如下:
public class Singleton {

    private static final Singleton instance = new Singleton();

    private Singleton() {

    }

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

3、適用場景及效果(優缺點)

     1、只需要1個實例,多了浪費,主要用於節約系統資源,創建一個對象需要消耗過多資源時,考慮將這個對象緩存,設計成單例的,如創建某些程序啓動配置對象讀取、操作系統的文件系統等,只需要創建一個就夠了,多了浪費;
     2、只需要1個實例,多了出錯,如計數器,唯一序列號生成器等;
     3、單例意味着多線程使用(如果單線程使用,單例完全沒有意義了),多線程下可以控制單一共享資源的訪問和線程間通訊,避免對同一資源的多重佔用,如僅有1個打印機,各個線程自行調用會對一個資源多重佔用,單例模式可以統一管理對打印機的訪問,還有如數據庫連接池、線程池、日誌應用等。
     4、大量無狀態的類實例,如需要大量靜態常量或方法(有時也可以定義成static)可以考慮使用單例模式,如web開發中的service層,都是業務無狀態的邏輯處理類,還有工具類和方法等,都可以設計成單例模式,這也是Spring框架中配置的bean默認都是單例的。
     優點(使用後的效果):
     1、單例只有一個實例,也只創建一次,可以節約系統資源,特別當這個對象需要頻繁地創建和銷燬時,而且創建和銷燬要比較多的資源時;
     2、能避免對單一資源的多重佔用,進行統一管理。
     3、單例模式可以在系統設置全局訪問點,優化和共享資源訪問。
     缺點:
     1、沒有接口,擴展困難,無法適應變化,基本上只能修改源碼。(爲什麼沒接口,就一個實例,接口沒意義);
     2、測試麻煩,單例沒完成,無法測試;
     3、與單一職責衝突。
     單例模式可以分爲有狀態的和無狀態的,無狀態的單例對象不可變的,一般就是提供一些工具方法,有狀態的單例對象是可變的,常用來給系統當作狀態庫,提供一些狀態,如序列號生成器等。

4、示例

    比如要做一個頁面計數器,可以使用單例模式,非常簡單,直接看代碼
//頁面計數器
public class PageCounter {

    private static final PageCounter instance = new PageCounter();
    // 計數器
    private AtomicLong counter = new AtomicLong(0);

    private PageCounter() {

    }

    public static PageCounter getInstance() {
        return instance ;
    }

    public void add() {
        counter.getAndAdd(1);
    }

    public long get() {
        return counter.get();
    }

}

5、模式擴展

     說到單例模式,很多人想到的是如何創建單例模式,有很多種創建方法,懶漢、惡漢、雙重鎖等等,此處大概介紹一下。
第一種(餓漢)
//餓漢模式(推薦),類加載時就創建了
//優點:1、線程安全;2、調用getInstance時速度快
//缺點:1、無法延遲加載;2、有可能浪費資源,無人調用getInstance()時,仍然創建了實例
public class Singleton01 {

    private static final Singleton01 instance = new Singleton01();

    private Singleton01() {
    }

    public static Singleton01 getInstance() {
        return instance ;
    }
}
第二種(餓漢變種)
//餓漢模式變種,類加載時就創建了,和上一個模式區別不大,只是能在static中加入邏輯處理
public class Singleton02 {

    private static Singleton02 instance = null;

    static {
        // 此處可以寫一些邏輯
        instance = new Singleton02();
    }

    private Singleton02() {
    }

    public static Singleton02 getInstance() {
        return instance ;
    }
}
第三種(懶漢)
//懶漢(線程不安全),用到了再去初始化
//優點:延遲加載
//缺點:致命的併發問題,可能導致創建多次
public class Singleton03 {

    private static Singleton03 instance = null;

    private Singleton03() {
    }

    public static Singleton03 getInstance() {
        if ( instance == null ) {
            // 此處有併發問題
            instance = new Singleton03();
        }
        return instance ;
    }
}
第四種(懶漢變種)
//懶漢(線程安全)
//優點:延遲加載
//缺點:效率低下,初始化完畢後,getInstance()方法根本不需要同步了
public class Singleton04 {

    private static Singleton04 instance = null;

    private Singleton04() {
    }

    public synchronized static Singleton04 getInstance() {
        if ( instance == null ) {
            instance = new Singleton04();
        }
        return instance ;
    }
}
第五種(雙重鎖定檢查)
//雙重鎖定檢查
//優點:延遲加載,效率高
//缺點:jdk1.5之後纔可以用
//由於jdk1.5之前編譯器允許處理器亂序執行,所以可能導致獲取到沒初始化完畢的instance
public class Singleton05 {

    private static Singleton05 instance = null;

    private Singleton05() {
    }

    public static Singleton05 getInstance() {
        if ( instance == null ) {
            synchronized (Singleton05.class) {
                if ( instance == null ) {
                    instance = new Singleton05();
                }
            }
        }
        return instance ;
    }
}
第六種(枚舉)
//枚舉方式(推薦),Effective Java作者Joshua Bloch推薦的方式
//優點:不僅能避免線程同步問題,還能防止反序列化生成新的對象,相當嚴謹
//最主要的是非常的簡單
//缺點:枚舉是jdk1.5之後加入的特性,對版本有要求
public enum Singleton06 {
    instance;

    public void someMethod() {
        // 業務邏輯方法
    }
}
第七種(靜態內部類)    
//靜態內部類
//優點:解決線程安全問題,而且可以延遲加載,基本上是曾經最好的辦法
//缺點:代碼複雜
public class Singleton07 {

    private static class RealSingleton {
        static final Singleton07 instance = new Singleton07();
    }

    public static Singleton07 getInstance() {
        return RealSingleton.instance;
    }

}
單例模式創建方法有很多種,沒有最好的,只有最合適的,比如第七種方法比較好,但是沒必要爲了一個不會出現的問題而使用很複雜的第七種模式,如果沒有需要延遲加載的地方(如讀取配置文件等),推薦第一種模式,如果是JDK1.5以上,推薦使用枚舉的方法。
     單例模式還有個地方要注意,只有1個實例,雖然構造函數私有化,外邊不能new了,但是還有其他方式創建對象實例,如反序列化時,可能得到另一個實例,此時就要考慮序列化對單例的影響,還有不同類加載器(ClassLoader)對單例的影響等都要考慮。
     其實就是創建方式要支持的級別,這就需要根據實際情況,選擇你的創建方式了:
     1、每次從getInstance()都能返回一個且唯一的一個對象。
     2、希望這個方法能適應多線程併發訪問。
     3、併發時方法性能儘可能高。
     4、實現延遲加載(Lazy Load),在需要的時候才被構造,而且要能夠處理業務邏輯。
     5、能夠處理多ClassLoader、多JVM,防止反序列化等情況。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章