1. 程式人生 > >設計模式——建立型——單例模式

設計模式——建立型——單例模式

一、單例模式定義:

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

二、單例模式特點:


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

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

1、餓漢式(靜態常量)[可用]

public class Singleton {

    private final static Singleton INSTANCE = new Singleton();

    private Singleton(){}

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

優點:這種寫法比較簡單,就是在類裝載的時候就完成例項化。避免了執行緒同步問題。

缺點:在類裝載的時候就完成例項化,沒有達到Lazy Loading的效果。如果從始至終從未使用過這個例項,則會造成記憶體的浪費。

2、餓漢式(靜態程式碼塊)[可用]

public class Singleton {

    private static Singleton instance;

    static {
        instance = new Singleton();
    }

    private Singleton() {}

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

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

3、懶漢式(執行緒不安全)[不可用]

public class Singleton {

    private static Singleton singleton;

    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

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

4、懶漢式(執行緒安全,同步方法)[不推薦用]

public class Singleton {

    private static Singleton singleton;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

解決上面第三種實現方式的執行緒不安全問題,做個執行緒同步就可以了,於是就對getInstance()方法進行了執行緒同步。

缺點:效率太低了,每個執行緒在想獲得類的例項時候,執行getInstance()方法都要進行同步。而其實這個方法只執行一次例項化程式碼就夠了,後面的想獲得該類例項,直接return就行了。方法進行同步效率太低要改進。

5、懶漢式(執行緒安全,同步程式碼塊)[不可用]

public class Singleton {

    private static Singleton singleton;

    private Singleton() {}

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

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

6、雙重檢查[推薦用]

public class Singleton {

    private static volatile Singleton singleton;

    private Singleton() {}

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

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

優點:執行緒安全;延遲載入;效率較高。

 1.相應的基礎知識

  •  什麼是類級內部類?

  簡單點說,類級內部類指的是,有static修飾的成員式內部類。如果沒有static修飾的成員式內部類被稱為物件級內部類。

  類級內部類相當於其外部類的static成分,它的物件與外部類物件間不存在依賴關係,因此可直接建立。而物件級內部類的例項,是繫結在外部物件例項中的。

  類級內部類中,可以定義靜態的方法。在靜態方法中只能夠引用外部類中的靜態成員方法或者成員變數。

  類級內部類相當於其外部類的成員,只有在第一次被使用的時候才被會裝載。

  •  多執行緒預設同步鎖的知識

  大家都知道,在多執行緒開發中,為了解決併發問題,主要是通過使用synchronized來加互斥鎖進行同步控制。但是在某些情況中,JVM已經隱含地為您執行了同步,這些情況下就不用自己再來進行同步控制了。這些情況包括:

  1.由靜態初始化器(在靜態欄位上或static{}塊中的初始化器)初始化資料時

  2.訪問final欄位時

  3.在建立執行緒之前建立物件時

  4.執行緒可以看見它將要處理的物件時

  2.解決方案的思路

  要想很簡單地實現執行緒安全,可以採用靜態初始化器的方式,它可以由JVM來保證執行緒的安全性。比如前面的餓漢式實現方式。但是這樣一來,不是會浪費一定的空間嗎?因為這種實現方式,會在類裝載的時候就初始化物件,不管你需不需要。

  如果現在有一種方法能夠讓類裝載的時候不去初始化物件,那不就解決問題了?一種可行的方式就是採用類級內部類,在這個類級內部類裡面去建立物件例項。這樣一來,只要不使用到這個類級內部類,那就不會建立物件例項,從而同時實現延遲載入和執行緒安全。

7、靜態內部類[推薦用]

public class Singleton {

    private Singleton() {}

    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

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

這種方式跟餓漢式方式採用的機制類似,但又有不同。兩者都是採用了類裝載的機制來保證初始化例項時只有一個執行緒。不同的地方在餓漢式方式是隻要Singleton類被裝載就會例項化,沒有Lazy-Loading的作用,而靜態內部類方式在Singleton類被裝載時並不會立即例項化,而是在需要例項化時,呼叫getInstance方法,才會裝載SingletonInstance類,從而完成Singleton的例項化。

類的靜態屬性只會在第一次載入類的時候初始化,所以在這裡,JVM幫助我們保證了執行緒的安全性,在類進行初始化時,別的執行緒是無法進入的。

優點:避免了執行緒不安全,延遲載入,效率高。

8、列舉[推薦用]

public enum Singleton {
    INSTANCE;
    public void whateverMethod() {

    }
}

藉助JDK1.5中新增的列舉來實現單例模式。不僅能避免多執行緒同步問題,而且還能防止反序列化重新建立新的物件。可能是因為列舉在JDK1.5中才新增,所以在實際專案開發中,很少見人這麼寫過。

優點

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

缺點

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

適用場合

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