1. 程式人生 > 其它 >建立類模式-單例模式

建立類模式-單例模式

1.單例模式的定義

單例模式確保某個類只有一個例項,而且自行例項化並向整個系統提供這個例項。在計算機系統中,執行緒池、快取、日誌物件、對話方塊、印表機、顯示卡的驅動程式物件常被設計成單例。這些應用都或多或少具有資源管理器的功能。每臺計算機可以有若干個印表機,但只能有一個Printer Spooler,以避免兩個列印作業同時輸出到印表機中。每臺計算機可以有若干通訊埠,系統應當集中管理這些通訊埠,以避免一個通訊埠同時被兩個請求同時呼叫。總之,選擇單例模式就是為了避免不一致狀態,避免政出多頭。

2.單例模式的特點 

1、單例類只能有一個例項。
2、單例類必須自己建立自己的唯一例項。
3、單例類必須給所有其他物件提供這一例項。

單例模式保證了全域性物件的唯一性,比如系統啟動讀取配置檔案就需要單例保證配置的一致性。

3.執行緒安全問題

一方面在獲取單例的時候,要保證不能產生多個例項物件,後面會詳細講到八種實現方式;
另一方面,在使用單例物件的時候,要注意單例物件內的例項變數是會被多執行緒共享的,推薦使用無狀態的物件,不會因為多個執行緒的交替排程而破壞自身狀態導致執行緒安全問題,比如我們常用的VO,DTO等(區域性變數是在使用者棧中的,而且使用者棧本身就是執行緒私有的記憶體區域,所以不存線上程安全問題)。

4.實現單例模式的八種方式

4.1餓漢式(靜態常量)【可用】

優點:這種寫法比較簡單,就是在類裝載的時候就完成例項化。避免了執行緒同步問題。
缺點:在類裝載的時候就完成例項化,沒有達到Lazy Loading的效果。如果從始至終從未使用過這個例項,則會造成記憶體的浪費。

/**
 * @author 民宿
 * @dscription 餓漢式(靜態常量)【可用】
 */
public class Singleton1 {
    private static final Singleton1 INSTANCE = new Singleton1();

    private Singleton1() {
        
    }
    
    public static Singleton1 getInstance() {
        return INSTANCE;
    }
}

4.2餓漢式(靜態程式碼塊)【可用】

這種方式和上面的方式其實類似,只不過將類例項化的過程放在了靜態程式碼塊中,也是在類裝載的時候,就執行靜態程式碼塊中的程式碼,初始化類的例項。優缺點和上面是一樣的。

/**
 * @author 民宿
 * @dscription 餓漢式(靜態程式碼塊)【可用】
 */
public class Singleton2 {
    private static Singleton2 INSTANCEOF;

    static {
        INSTANCEOF = new Singleton2();
    }

    private Singleton2() {

    }

    public static Singleton2 getInstance() {
        return INSTANCEOF;
    }
}

4.3懶漢式(執行緒不安全)【不可用】

這種寫法起到了Lazy Loading的效果,但是隻能在單執行緒下使用。如果在多執行緒下,一個執行緒進入了if (singleton == null)判斷語句塊,還未來得及往下執行,另一個執行緒也通過了這個判斷語句,這時便會產生多個例項。所以在多執行緒環境下不可使用這種方式。

/**
 * @author 民宿
 * @dscription 懶漢式(執行緒不安全)【不可用】
 */
public class Singleton3 {
    private static Singleton3 INSTANCE = null;

    private Singleton3() {
        
    }
    
    public static Singleton3 getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton3();
        }
        
        return INSTANCE;
    }
}

4.4懶漢式(執行緒安全,同步方法)【不推薦用】

解決上面第三種實現方式的執行緒不安全問題,做個執行緒同步就可以了,於是就對getInstance()方法進行了執行緒同步。
缺點:效率太低了,每個執行緒在想獲得類的例項時候,執行getInstance()方法都要進行同步。而其實這個方法只執行一次例項化程式碼就夠了,後面的想獲得該類例項,直接return就行了。方法進行同步效率太低要改進。

/**
 * @author 民宿
 * @dscription 懶漢式(執行緒安全,同步方法)【不推薦用】
 */
public class Singleton4 {
    private static Singleton4 INTANCE = null;

    private Singleton4() {
        
    }

    public static synchronized Singleton4 getInstance() {
        if (INTANCE == null) {
            INTANCE = new Singleton4();
        }
        
        return INTANCE;
    }
}

4.5懶漢式(執行緒不安全,同步程式碼塊)【不可用】

由於第四種實現方式同步效率太低,所以摒棄同步方法,改為同步產生例項化的的程式碼塊。但是這種同步並不能起到執行緒同步的作用。跟第3種實現方式遇到的情形一致,假如一個執行緒進入了if (singleton == null)判斷語句塊,還未來得及往下執行,另一個執行緒也通過了這個判斷語句,這時便會產生多個例項。

/**
 * @author 民宿
 * @dscription 懶漢式(執行緒不安全,同步程式碼塊)【不可用】
 */
public class Singleton5 {
    private static Singleton5 INSTANCE;

    private Singleton5() {

    }

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

4.6雙重檢查【推薦使用】

Double-Check概念對於多執行緒開發者來說不會陌生,如程式碼中所示,我們進行了兩次if (singleton == null)檢查,這樣就可以保證執行緒安全了。這樣,例項化程式碼只用執行一次,後面再次訪問時,判斷if (singleton == null),直接return例項化物件。
優點:執行緒安全;延遲載入;效率較高。

/**
 * @author 民宿
 * @dscription 雙重檢查【推薦使用】
 */
public class Singleton6 {
    private static volatile Singleton6 INSTANCE;

    private Singleton6() {

    }

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

4.7靜態內部類【推薦使用】

這種方式跟餓漢式方式採用的機制類似,但又有不同。兩者都是採用了類裝載的機制來保證初始化例項時只有一個執行緒。不同的地方在餓漢式方式是隻要Singleton類被裝載就會例項化,沒有Lazy-Loading的作用,而靜態內部類方式在Singleton類被裝載時並不會立即例項化,而是在需要例項化時,呼叫getInstance方法,才會裝載SingletonInstance類,從而完成Singleton的例項化。
類的靜態屬性只會在第一次載入類的時候初始化,所以在這裡,JVM幫助我們保證了執行緒的安全性,在類進行初始化時,別的執行緒是無法進入的。
優點:避免了執行緒不安全,延遲載入,效率高。

/**
 * @author 民宿
 * @dscription 靜態內部類(執行緒安全)【推薦使用】
 */
public class Singleton7 {
    private Singleton7() {
        
    }

    private static class Singleton7Instance {
        private static final Singleton7 INSTANCE = new Singleton7();
    }

    public static Singleton7 getInstance() {
        return Singleton7Instance.INSTANCE;
    }
}

4.8列舉【推薦使用】

藉助JDK1.5中新增的列舉來實現單例模式。不僅能避免多執行緒同步問題,而且還能防止反序列化重新建立新的物件。

/**
 * Author 民宿
 * Description 單例模式 列舉式 此種方式是執行緒最安全的
 */
public class SingletonEnum {
    private SingletonEnum() {

    }

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

    private enum Singleton {
        INSTANCE;
        private SingletonEnum instance = null;

        // JVM保證此方法絕對只調用一次
        Singleton() {
            instance = new SingletonEnum();
        }

        public SingletonEnum getInstance() {
            return instance;
        }
    }
}

5.單例模式的優點

系統記憶體中該類只存在一個物件,節省了系統資源,對於一些需要頻繁建立銷燬的物件,使用單例模式可以提高系統性能。

6.單例模式的缺點

當想例項化一個單例類的時候,必須要記住使用相應的獲取物件的方法,而不是使用new,可能會給其他開發人員造成困擾,特別是看不到原始碼的時候。

7.單例模式的使用場景

• 需要頻繁的進行建立和銷燬的物件;
• 建立物件時耗時過多或耗費資源過多,但又經常用到的物件;
• 工具類物件;
• 頻繁訪問資料庫或檔案的物件。