單例模式定義
單例模式確定一個類隻有一個執行個體,并提供一個全局通路點。
經典的單例模式模型
// NOTE: This is not thread safe!
public class Singleton {
private static Singleton uniqueInstance;
// other useful instance variables here
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
// other useful methods here
}
1)私有的構造函數確定了外部無法建立該類。
2)利用一個靜态私有的自身類變量來儲存自身的唯一執行個體。
3)公有的靜态方法提供了入口,用類名可以直接調用
4)首次調用getIntance()時,會建立該類的執行個體并儲存該執行個體
5)再次調用getIntance()時,不會建立該類的執行個體,直接調用儲存的執行個體
3)4)5)點優點:靜态通路(類似全局變量),延遲執行個體化;缺點:線程不安全
當使用多線程時,如果多個線程同時走到
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
就會同時建立多個執行個體。
那怎麼辦呢?
方法1)
隻要把getInstance() 這個全局通路點改成同步即可
public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
synchronized 關鍵字可以迫使每個線程進入到這個方法之前,都要先等候其它線程離開該方法,也就是說不會有兩個線程可以同時進入到這個方法。
但問題來了:
隻有當執行個體變量沒有建立之前這個同步是真正需要的,當執行個體被建立出來之後,這個同步就變的沒有意義了;
你要知道,同步會使用程式效率降低。
如果getInstance()不是太頻繁,它的性能對程式不是很關鍵,可以使用這個方法不用理會。
但如果getInstance()太頻繁,你就要優化了。
是以
解決方案:
方法2)立即建立執行個體,而不是延遲執行個體化
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
}
這種做法使JVM在加載這個類時馬上建立此類的唯一的單例執行個體。這樣保證在任何線程通路執行個體變量時,執行個體變量一定先建立了。
方法3)雙重檢查加鎖(double-checked locking),減少 getInstance()中使用同步
要點:檢查執行個體是否已建立,如果未建立,才同步
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) { //檢查執行個體,如果不存在,就進入同步區域
synchronized (Singleton.class) { // 隻有第一次才會執行這裡的同步區域
if (uniqueInstance == null) { // 進入區域後,再檢查一次,如果仍是null,才建立執行個體
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
volatile 關鍵字可以確定變量被真正讀取或儲存。
這個做法可以幫助我們大大減少getInstance()時間上的耗費。
3種方法的适用性
方法1):保證可行的最直接做法,沒有對性能上的任何考慮
方法2):靜态初始化執行個體,直接先執行個體化一個執行個體(看似先浪費了記憶體),但這個執行個體我們一定會建立,也不是不能接受
方法3):確定使用Java5以上版本。