今天我們來看一下單件模式,這個模式是所有模式中類圖最簡單的哦! 為什麼用單件模式: 有些對象我們只需要一個,比如:連接池、緩存、對話框、和註冊表對象、日誌對 象等對象。事實上,這類對象只能有一個實例,如果製造出多個實例,就會導致許 多問題產生,例如:程式的行為異常、資源使用過量,或者是不一致的結果。 ...
今天我們來看一下單件模式,這個模式是所有模式中類圖最簡單的哦!
為什麼用單件模式:
有些對象我們只需要一個,比如:連接池、緩存、對話框、和註冊表對象、日誌對
象等對象。事實上,這類對象只能有一個實例,如果製造出多個實例,就會導致許
多問題產生,例如:程式的行為異常、資源使用過量,或者是不一致的結果。也就
是為了防止多次 New 對象。
從一個簡單的單件模式入門:
1 public class Singleton { 2 private static Singleton uniqueInstance; 3 4 // other useful instance variables here 5 6 private Singleton() {} 7 8 public static Singleton getInstance() { 9 if (uniqueInstance == null) { 10 uniqueInstance = new Singleton(); 11 } 12 return uniqueInstance; 13 } 14 15 // other useful methods here 16 public String getDescription() { 17 return "I'm a thread safe Singleton!"; 18 } 19 }
在這裡主要註意的點有:
第二行:利用一個靜態變數來記錄Singleton類的唯一實例;
第六行:把構造器聲明為私有的,只有字Singleton類內才可以調用構造器;
第八行至第十三行:用getInstance()方法實例化對象並返回這個示例(如果
uniqueInstance是空的則利用私有構造器產生一個Sin-
gleton實例,否則表示已經有了實例,並將uinqueIns-
tance當返回值)。
讓我們寫一個測試類(Main.java):
1 public class Main { 2 3 public static void main(String[] args) { 4 Singleton singleton = Singleton.getInstance(); 5 System.out.println(singleton.getDescription());
6 } 7 }
結果展示:
單件模式:確保一個類只有一個實例,並提供一個全局訪問點。
好啦,我們從上面的示例簡單學習了單件模式;現在讓我們來看一個更加複雜的示例
(巧克力工廠):
1 public class ChocolateBoiler { 2 private boolean empty; 3 private boolean boiled; 4 5 private ChocolateBoiler() { 6 empty = true; //剛開始鍋爐是空的 7 boiled = false; 8 } 9 10 public void fill() { 11 if (isEmpty()) { //在鍋爐內填入原料時,鍋爐必須是空的。一旦填入原料,就把empty和boiled標誌設置好 12 empty = false; 13 boiled = false; 14 //在鍋爐內填滿巧克力和牛奶的混合物 15 } 16 } 17 18 public void drain() {//鍋爐排出時,必須是滿的(不可以是空的)而且是煮沸過的。排出完畢後,吧empty設置為true 19 if (!isEmpty() && isBoiled()) { 20 // 排出煮沸的巧克力和牛奶 21 empty = true; 22 } 23 } 24 25 public void boil() { //煮混合物時,鍋爐必須是滿的,並且是沒有煮過的。一旦煮沸後,就把boiled設為true 26 if (!isEmpty() && !isBoiled()) { 27 // 將爐內物煮沸 28 boiled = true; 29 } 30 } 31 32 public boolean isEmpty() { 33 return empty; 34 } 35 36 public boolean isBoiled() { 37 return boiled; 38 } 39 }
我們在有意識地防止不好的事情發生,但是如果同時存在兩個ChocolateBoiler實例,
可能就會發生很糟糕的事情哦!
所以我們把這個類設計成單件:
1 public class ChocolateBoiler { 2 private boolean empty; 3 private boolean boiled; 4 private static ChocolateBoiler uniqueInstance; 5 6 private ChocolateBoiler() { 7 empty = true; 8 boiled = false; 9 } 10 11 public static ChocolateBoiler getInstance() { 12 if (uniqueInstance == null) { 13 System.out.println("Creating unique instance of Chocolate Boiler"); 14 uniqueInstance = new ChocolateBoiler(); 15 } 16 System.out.println("Returning instance of Chocolate Boiler"); 17 return uniqueInstance; 18 } 19 20 public void fill() { 21 if (isEmpty()) { 22 empty = false; 23 boiled = false; 24 // fill the boiler with a milk/chocolate mixture 25 } 26 } 27 28 public void drain() { 29 if (!isEmpty() && isBoiled()) { 30 // drain the boiled milk and chocolate 31 empty = true; 32 } 33 } 34 35 public void boil() { 36 if (!isEmpty() && !isBoiled()) { 37 // bring the contents to a boil 38 boiled = true; 39 } 40 } 41 42 public boolean isEmpty() { 43 return empty; 44 } 45 46 public boolean isBoiled() { 47 return boiled; 48 } 49 }
測試類(Main.java):
1 public class ChocolateController { 2 public static void main(String args[]) { 3 ChocolateBoiler boiler = ChocolateBoiler.getInstance(); 4 boiler.fill(); 5 boiler.boil(); 6 boiler.drain(); 7 8 // 將返回已存在的實例,也就是boiler 9 ChocolateBoiler boiler2 = ChocolateBoiler.getInstance(); 10 } 11 }
我們現在模仿了第一個項目,把它做成了單件,但是現在的這個類完美嗎?不!當然
不完美,這不問題出現了:這個機器竟然允許在加熱的時候繼續加原料。
我們現在化身為JVM老看看問題出在哪裡吧:
現在讓我們開始解決問題,處理多線程(延遲同步,讀完下麵按段話就懂嘍):
方法①:
1 public class Singleton { 2 private static Singleton uniqueInstance; 3 4 private Singleton() {} 5 6 public static synchronized Singleton getInstance() { 7 if (uniqueInstance == null) { 8 uniqueInstance = new Singleton(); 9 } 10 return uniqueInstance; 11 } 12 13 //其他代碼 14 }
第六行:通過增加synchronized關鍵字到getInstance()方法中,我們迫使每個線程在
進入這個方法之前,要先等候別的線程離開該方法。也就是說,不會有兩個線程可同時
進入這個方法。
但是,我們是否能改善多線程呢?
方法②(使用“急切”創建實例,而不是延遲實例的方法):
1 public class Singleton { 2 private static Singleton uniqueInstance = new Singleton(); 3 4 private Singleton() {} 5 6 public static synchronized Singleton getInstance() { 7 return uniqueInstance; 8 } 9 10 //其他代碼 11 }
方法③(雙重檢查加鎖,在getInstance()中減少使用同步):
1 public class Singleton { 2 private volatile static Singleton uniqueInstance; 3 4 private Singleton() {} 5 6 public static synchronized Singleton getInstance() { 7 if (uniqueInstance == null) { 8 synchronized(Singleton.class){ 9 if(uniqueInstance == null){ 10 uniqueInstance = new Singleton(); 11 } 12 } 13 } 14 return uniqueInstance; 15 } 16 17 //其他代碼 18 }
註:volatile關鍵詞確保:當uniqueInstance變數被初始化成Singleton實例時,多個
縣城正確的處理uniqueInstance變數。
我們現在來對比一下三個方法:
方法①同步getInstance方法:
這是保證可行的最直接的做法,對於巧克力鍋爐似乎沒有性能的考慮,所
以可以用這個方法
方法②急切實例化:
我們一定需要用到一個巧克力鍋爐,所以靜態的初始化實力並不是不行的。
雖然對於採用標準模式的開發人員來說,此做法可能稍微陌生一點兒。但也
是可行的。
方法③雙重檢查加鎖:
由於沒有性能上的考慮,所以這個方法似乎殺雞用了牛刀。另外,採用這個方法還得確定使用的是Java5以上的版本。