淺談設計模式——單例模式

城北有個混子發表於2020-11-03

單例模式

  單例模式(Singleton)是一種常用的設計模式,它是建立型模式的一種,適用於一個類有且只有一個例項的情況,也就是說,單例模式確保了某個類只有一個例項(物件)存在。

單例模式定義的三個要素

  ① 定義私有的靜態成員。

  ② 建構函式私有化。

  ③ 提供一個公有的靜態方法以構造例項。

單例模式的實現方式

  對於單例模式,一定要考慮併發狀態下的同步問題,單例模式根據例項化物件時間的不同在實現程式碼時分為兩種主流的實現方式,一種叫作餓漢式單例,另一種叫作懶漢式單例,這兩種實現方式都是多執行緒安全的,但前者是天生多執行緒安全。

  ★ 餓漢式單例的實現方式:在單例類被載入時,就例項化一個物件。

  ★ 懶漢式單例的實現方式:呼叫取得例項的方法時才會例項化物件。

餓漢式單例模式的Java 實現程式碼如下:

// 單例模式(餓漢式)
public class Singleton {
    
    private static Singleton instance = new Singleton();
    private Singleton() {};
    public static Singleton getInstance() {
        return instance;
    }
}

懶漢式單例模式的Java 實現程式碼如下:

//單例模式(懶漢式)
public class Singleton {
    
    private static Singleton instance;
    private Singleton() {};
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

  在Java中,因為餓漢式實現方案天生執行緒安全,而懶漢式需要加號synchronized 關鍵字,影響的了效能,所以餓漢式單例效能要優子懶漢式單例。

單例模式的優點

  ① 避免了頻繁地建立和銷燬物件,減少了系統開銷。

  ② 節省了記憶體空間,在記憶體中只有一個物件。

  ③ 提供了一個全域性訪問點。

單例模式的適用場景

  ✔ 針對某些需要頻繁建立物件又頻繁銷燬物件的類。

  ✔ 需要經常用到物件,但建立時消耗大量資源。

  ✔ 針對確實只能建立一個物件的情況,比如某些核心交易類,只允許保持一個物件。

單例模式程式碼測試

編寫懶漢式單例模式:

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

編寫main方法:

public class Test {

    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        if (s1 == s2) {
            System.out.println("s1 和 s2是同一個例項");
        }else {
            System.out.println("s1 和 s2是不同的例項");
        }
    }
}

執行結果:

  由此可以看出,單例模式只會建立一個例項物件。

  單例模式對例項的物件做出來限制,為什麼要限制呢?因為在一些情況下,當存在多個例項時,例項之間會相互影響,可能產生意想不到的Bug;但是,當我們可以確保只存在一個例項時,在這個前提下程式就可以放心的執行了。

常見問題

單例模式兩種實現方式的區別?

  單例模式主要有兩種實現方式:餓漢式和懶漢式,兩種實現方式是有區別的。

  餓漢式是在載入類時就建立了類的一個物件;而懶漢式則是在呼叫例項方法getInstance()才會建立一個物件。

懶漢式單例模式的劣勢?

  從多執行緒的角度,懶漢式單例模式是不安全的,所以為了保障執行緒安全,一般的實現方式是在例項建立的方法getlnstance()前加 synchronized 保障執行緒安全,即: public static synchronized Singleton getlnstance(){} 。這種實現方案雖然簡單,但是缺點也比較明顯;這種實現方式降低了整個例項化的效能。

如何改進懶漢式單例模式?

  不要在getlnstance()方法上進行同步,而是在方法內部進行同步。

具體操作如下:

  ① 進入方法後先檢查例項是否已經存在,如果不存在則進入同步塊;如果存在則直接返回該例項。

  ② 如果進入了同步塊,則再次檢查例項是否存在,如果不存在,就在同步塊中建立例項;如果存在則直接返回該例項。

  這種方法因為要經過兩次“檢查”,所以被稱為“雙重檢查加鎖機制”,這種方案既能實現執行緒安全,又能最大限度地減少效能影響。

雙重檢查加鎖機制的實現程式碼:

public class Singleton {

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

  需要注意的是,雙重檢查加鎖機制用到一個關鍵字 volatile,用volatile 關鍵字修飾的變數不會被本地執行緒快取,即變數的讀寫直接操作共享記憶體,這樣可以保證多個執行緒正確使用該變數。

單例模式總結

  單例模式有兩種實現方式:餓漢式(類載入時建立例項) 和 懶漢式(呼叫例項方法時建立例項)。

  單例模式只會建立一個例項,常用於某些核心類。

  單例模式中的餓漢式是天生執行緒安全的,懶漢式需要後天進行改進才會執行緒安全。需要注意的是懶漢式如何進行效能改進,即“雙重檢查加鎖機制”。

相關文章