iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 3
0
自我挑戰組

來讀設計模式:Junior developer 跟大家一起練功系列 第 20

DAY20: Singleton 模式與 Double-Checked Locking 模式

本篇會介紹

  • 何謂 singleton 模式
  • 用案例來看看如何實踐它
  • 何謂 double-checked locking 模式
  • 又如何實踐它

先簡單的說,這兩種模式都是用於確保某個類別只有一個物件被實體化。而它們的差別在於前這用在單執行緒的應用程式中;後者則用於多執行緒。

Singleton 模式

簡介 Singleton 模式

GoF 對 singleton 模式如此定義:

(Singleton 模式)保證一個類別僅有一個實體,並提供一個存取它的一個全域存取點。

Singleton 的工作原理如下:

  • 創造一個靜態的特殊 method,呼叫這個方法時,先檢查物件是否已被實體化,若已被實體化,直接回傳該物件的一個參照;尚未被實體化的話,就回傳一個新實體的參照。
  • 為了確保此方法是製造該物件的唯一方法,會將該類別的建構函數設定為私有。

來看案例:嘿又是電子商務

我們曾在 DAY10 談到繳稅規則。當時我們將不同的規則封裝進 Strategy 物件中,而這些物件都是一個 CalcTax 類別的衍生類別的實體化的結果。這代表我們可能會反覆地使用同樣的物件。

由於性能問題,我們希望不要一直反覆地實體化、銷毀這些物件。這時候就可以使用 singleton 模式。以下是 singleton 模式實踐的 Java 程式碼:

class AmericaTax extends Tax {
  	private static AmericaTax instance;
  	private AmericaTax() {
      	// 建構函數私有化
    }
  	public static AmericaTax getInstance() {
      	if (instance == null) {
          	instance = new AmericaTax();
        }
      	return instance;
    }
}

在本案例中, SalesOrder 會去詢問 Tax 物件應該使用哪種 Tax 物件。在這裡呈現了兩種封裝:

  1. 隱藏使用哪些具體類別
  2. 隱藏每個類別進行了多少次實體化

在此我們會用上多型,程式碼如下。

abstract class Tax {
  	private static Tax instance;
  	protected Tax() {}
  	abstract double calcTax(double qty, double price);
  	public static Tax getInstance() {
      	// 根據相應規則建立自己的物件
     		// 此處先單純建立 AmericaTax 物件
      	instance = AmericaTax.getInstace();
    }
  	
  	public class AmericaTax extends Tax {
      	private static AmericaTax instance;
      	private AmericaTax() {}
      	public static Tax getInstance() {
          	if (instance == null) {
            		instance = new AmericaTax();
            }
          	return instance;
        }
    }
}

Singleton 模式的關鍵特徵

以下是這個模式的關鍵特徵。

項目 內容
意圖 希望物件只有一個實體。
問題 幾個不同客戶物件需要參照同一物件,而且希望確保這些類型的物件數目不超過一個。
解決方案 保證只有一個實體。
參與者與協作者 客戶物件只能透過 getInstance() 建立 Singleton 實體。
效果 客戶物件無須操心是否已存在 Singleton 實體。
實作 1. 增加一個類別的私有靜態成員,參照所需的物件(初值為 null)2, 增加一個公共靜態 method,當成員變數為 null 時實體化該類別,然後返回該成員變數的值3. 將建構函數設為保護或私有,防止其他人能夠直接實體化該類別。

以下是 UML 圖:

Double-Checked Locking 模式

如同一開始說的,double-checked locking 模式僅適用於多執行緒的應用程式。我們先回頭思考 singleton 模式在多執行緒中,會出現什麼問題。

我們假設一種情境:

  1. 第一個執行緒檢查實體是否存在。因為實體不存在,該執行緒執行建立第一個實體
  2. 假設當實體化化完成前,另一個執行緒也進來檢查 instance 是某為 null。因為第一個執行緒尚未建立(instance 仍是 null),所以第二個執行緒也建立了一個新實體
  3. 因此,兩個執行緒都執行了 Singletonnew 操作,從而建立了兩個物件

假設 Singleton 有狀態,而且我們希望當狀態被改變時,其他物件都應該要注意到,那麼上述的情境就會帶來困擾。

我們可能會想說:只要同步化對 Singleton 物件是否已建立的操作即可。但這樣的同步化會使得所有執行緒都會必須等待關於物件是否存在的檢查。有可能會拖住效能。

另一種想法是把同步化程式碼放進 if(instance == null)判斷句後,但其實這也沒有用,因為還是有可能會有建立兩個 Singleton 物件的可能。

引入 double-checked locking 模式

這個模式的解決方案會是:當檢查到 null 時(進入判斷句)進行同步,再檢查一次以確保實體尚未建立。此模式的意圖是優化掉不必要的鎖定。Java 的程式碼如下:

class AmericaTax extends Tax {
		private static AmericaTax instance;
  	private AmericaTax() {}
  	public static Tax getInstance() {
      	if (instance == null) {
          	synchronized (this) {
              	doSync();
            }
        }
      	return instance;
    }
  	private synchronized static void doSync() {
      	if (instance == null) instance = new AmericaTax();
    }
}

Double-checked locking 模式的特點如下:

  • 建立物件前,增加一次檢查,避免不必要的鎖定
  • 支援多執行緒環境

它對 Java 不適用

但此模式對 Java 其實並不適用,這原因來自於 Java 編譯器的特性:它本身的優化工作會在構造方法實體化物件前從構造方法返回指向該物件的參照,a.k.a 當 AmericaTax 物件完全構造之前,doSync() 可能就已經完成了。

本書作者提出了一個 Java 適用的解決方案:

class AmericaTax extends Tax {
		private static class Instance {
      	static final Tax instance = new AmericaTax();
    }
  	private static AmericaTax instance;
  	private AmericaTax() {}
  	public static Tax getInstance() {
      	return Instance.instance;
    }
}

因為內部類別只會被裝載一次,所以只會建立一個 AmericaTax 物件。

接下來

下一篇,我們會介紹 Object pool 模式。先降~明天見!


上一篇
DAY19: 討論工廠模式
下一篇
DAY21: Object Pool 模式
系列文
來讀設計模式:Junior developer 跟大家一起練功22

1 則留言

0
Jason
iT邦新手 5 級 ‧ 2021-08-25 11:53:57

雖然程式我看不懂
但這裏說的很棒
Singleton 模式的關鍵特徵
以下是這個模式的關鍵特徵。
項目,意圖....

我要留言

立即登入留言