獨體模式 Singleton
確保一個類別只有一個實體,並給它一個存取的全域點(global point)。
有些物件只需要一個實體,如果有多個實體同時存在,反而會造成問題,如 memory pool、garbage collector... 等。
全域物件
最簡單的作法可以採用一個全域物件。但這需要透過程式員之間的約定並嚴格遵守才能達成;並且,這個全域物件在程式開始執行時就建立好了,如果一直都沒用到,反而造成資源無謂地浪費。
物件的出生
物件產生 Q&A:
Q: 一個物件如何產生?
A: 對於 C++ 而言,有兩種方式。一種是直接宣告變數式的 Object obj,一種是動態產生 Object* obj = new Object();而對於 Java 而言,只有一種:new Object()。無論哪種方式,都會配置記憶體,然後以 constructor 來初始化物件。Q: 如何限制人們建立物件?
A: 將 constructors 宣告為 private,如此一來外部無人可以呼叫 constructors。Q: 那有誰可以建立物件?
A: 物件自己(物件本身的其他方法可以呼叫 constructors)。Q: 要如何取得一個這樣的物件實體?
A: 物件提供一個方法,稱為 getInstance(),這個方法會初始化一個物件並回傳。Q: 如果這個實體是一個 static 變數,會怎麼樣?
A: 那所有人都會存取到同一份實體。
獨體模式
獨體模式 Singleton就是可以確保某個類別只有一個實體的方法。將一個類別的 constructors 宣告為 private,並提供一個方法 getInstance() 讓外部取得物件的唯一實體。這個實體是一個 static 物件,若第一次呼叫時這個唯一實體沒有被初始化,則以 private constructor 來為它初始化。如果已經初始化完成了,就直接回傳這個 static 實體。這種在需要時才建立物件的技術,稱為 lazy instantiate。
Java 版本程式碼:
- public class Singleton
- {
- private static Singleton uniqueInstance;
- private Singleton() {} //private ctor
-
- public static Singleton getInstance() {
- if(uniqueInstance == null) {
- uniqueInstance = new Singleton();
- }
- return uniqueInstance;
- }
- }
C++版本:
- class Singleton
- {
- Singleton() {} //private ctor
-
- public:
- static Singleton& getInstance() {
- static Singleton uniqueInstance;
- return uniqueInstance;
- }
- };
這是獨體模式非常簡單的類別圖:
多緒環境的獨體模式
但是上述的獨體模式實踐,在多緒環境下會有 race condition 問題。
如 Java 版本第 9 行的判斷式,如果第一個執行緒判定條件成立並進入 if-block,但在產生實體前,就切換到第二個執行緒。則第二個執行緒也會判定條件成立並進入 if-block,造成兩個執行緒各執有一份實體,破壞 Singleton 的唯一性。
這種情況在 Java 有三種解決方式。
將 getInstance() 宣告為 synchronized 方法:
- public class Singleton
- {
- private static Singleton uniqueInstance;
- private Singleton() {} //private ctor
-
- public static synchoronized Singleton getInstance() { //加上 synchronized 關鍵字
- if(uniqueInstance == null) {
- uniqueInstance = new Singleton();
- }
- return uniqueInstance;
- }
- }
synchronized 關鍵字可以「同步化」一個方法 —— 同時只有一個執行緒可以進入此方法執行。在此執行緒結束之前,其他執行緒只得等待。缺點:每一次呼叫 getInstance() 都得同步化執行(事實上只有第一次需要),效能較差。
「率先」建立實體:
- public class Singleton
- {
- private static Singleton uniqueInstance = new Singleton();
-
- private Singleton() {}
-
- public static Singleton getInstance() {
- return uniqueInstance;
- }
- }
以 static initializer 來建立 uniqueInstance 物件(而不使用 lazy instantiate)。這麼一來 uniqueInstance 在 JVM 載入此類別時就會被建立,無 race condition 問題。缺點:一開始就得建立實體,若不使用則徒然浪費資源。
Double-checked lock:
- public class Singleton
- {
- private volatile static Singleton uniqueInstance; //加上 volatile 關鍵字
-
- private Singleton() {}
-
- public static Singleton getInstance() {
- if(uniqueInstance == null) { //如果實體不存在,才會進入下面的同步區塊
- // ---- synchronized block ---- 同步區塊會 atomic 執行
- synchronized(Singleton.class) {
- if(uniqueInstance == null) { //再做一次檢查,怕在進入前有其他執行緒趁隙將實體建立
- uniqueInstance = new Singleton();
- }
- }
- // ---- synchronized block ----
- }
- return uniqueInstance;
- }
- }
以 volatile 關鍵字 uniqueInstance 宣告為多工處理的共用變數,並以 synchronized block 讓檢查與建立的步驟 atomic 進行。但是加上一個 if 來讓此區塊只有「第一次」才會進入,兼顧了效能。這是效能最好的方案,但較複雜,且 Java 1.4 以前版本不適用。
From~http://notes.xamous.net/archives/139
沒有留言:
張貼留言