在開發過程中,其實很多情況下,都需要用到單例模式來維持對象的唯一性。
比如線程池、數據源、sessionFactory等。
一般的做法(懶漢式):
public class MyClass{
//餓漢式是直接聲明變量是就初始化:
//private static MyClass myClass=new MyClass();
private static MyClass myClass;
//將構造器聲明爲私有的,不允許外部類創建實例對象
private MyClass(){}
//聲明一個靜態方法來返或一個單例對象
public static MyClass getInstance(){
if(myClass == null)
myClass = new MyClass();
return myClass;
}
}
但是這個會在多線程下回導致數據不一致性。
這樣線程一跟線程二就獲得不同的實例對象,打破了唯一性。
這個問題就變爲多線程問題,一般通過加鎖是可以解決問題的,比如:
public class MyClass{
private static MyClass myClass;
//將構造器聲明爲私有的,不允許外部類創建實例對象
private MyClass(){}
//聲明一個靜態方法來返或一個單例對象
//通過增加synchronized來保證同一時刻只有一個線程可以執行getInstance()函數
public static synchronized MyClass getInstance(){
if(myClass == null)
myClass = new MyClass();
return myClass;
}
}
但是這種加鎖方法會帶來性能問題,因爲其實對於單例模式,只有在第一次初始化myClass的時候需要控制只有一個線程可以進行實例化對象,對於之後的獲得對象,由於已經實例化了,是可以直接返回的,但是由於synchronized的聲明就降低了性能。
這樣就引出了“雙重檢查加鎖”概念,代碼如下:
public class MyClass{
//增加volatile關鍵字來修飾"實例變量"
private volatile static MyClass myClass;
//將構造器聲明爲私有的,不允許外部類創建實例對象
private MyClass(){}
//聲明一個靜態方法來返或一個單例對象
//通過增加synchronized來保證同一時刻只有一個線程可以執行getInstance()函數
//將鎖的粒度降低
public static MyClass getInstance(){
if(myClass == null){
synchronized(this.class){
if(myClass == null){
myClass = new MyClass();
}
}
return myClass;
}
}
這樣就保證了只有當第一次需要實例化對象的時候纔會用到synchronized控制併發。
其實看到這,很多人會有一個疑問,好像沒必要用到volatile關鍵字啊。
我第一次看見也是這麼覺得,後來查看了一下資料,發現了“新大陸”。
主要原因就是編譯器爲了一下優化操作,會執行指令重排。具體原因可以參考博客:雙重檢查鎖定原理詳解
當然單例模式還有很多不同的實現方式,比如枚舉等。
以上內容爲《大話設計模式》的學習筆記