做爲一個好學習的程序開發者,應該會去學習優秀的開源框架,固然學習的過程當中難免會去閱讀源碼,這也是一個優秀程序員的必備素養,在學習的過程當中不少人會遇到的障礙,那就是設計模式。不少優秀的框架會運用設計模式來達到事半功倍的效果。鑑於本身以前對設計模式的生疏,在閱讀源碼時遇到設計模式的巧妙運用理解比較吃力。最近搞了一本新書 《圖解設計模式》(目測講的很基礎)開始學習設計模式,對從此學習源碼打下堅實的基礎。後續我在閱讀本書的過程當中,我將記錄下本身學習總結。今天就從最常使用的單例模式提及。javascript
在許多時候整個系統只須要擁有一個的全局對象,這樣有利於咱們協調系統總體的行爲。好比在某個服務器程序中,該服務器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取,而後服務進程中的其餘對象再經過這個單例對象獲取這些配置信息。這種方式簡化了在複雜環境下的配置管理。(維基百科)。java
在單例模式中,有一種稱爲懶漢式的單例模式。顧名思義,懶漢式能夠理解使用時才進行初始化,它包括私有的構造方法,私有的全局靜態變量,公有的靜態方法,是一種懶加載機制。程序員
public class Singleton {
private static Singleton instance;
private Singleton() {
System.out.println("初始化");
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}複製代碼
上面是最多見的懶漢式單例模式的寫法,可是若是在多線程的狀況下,上述方法就會出現問題,它達不到只有一個單例對象的效果,例如當某個線程1調用getInstance()方法並判斷instance == null
,此時(就在判斷爲空後new Singleton()以前)另外一個線程2也調用getInstance()方法,因爲此時線程1尚未new出對象,則線程2執行getInstance()中instance 也爲空,那麼此時就會出現多個實例的狀況,而達不到只有一個實例的目的。設計模式
在上述實現中咱們提到的懶漢式單例模式是一種非線程安全的,非線程安全即多線程訪問時會生成多個實例。那麼怎麼樣實現線程安全呢,也許你應該已經想到使用同步關鍵字synchronized。安全
public class Singleton {
private static Singleton instance;
private Singleton() {
System.out.println("初始化");
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}複製代碼
使用同步關鍵字後,也就實現了線程安全訪問,由於在任什麼時候候它只能有一個線程調用 getInstance() 方法。那麼你可能會發出疑問,這樣加入同步,在高併發狀況下,效率是很低的,由於真正須要同步的是咱們第一次初始化的時候,是的,因此咱們要進行進一步的優化。服務器
雙重檢測顧名思義就是兩次檢測,一次是檢測instance 實例是否爲空,進行第一次過濾,在同步快中進行第二次檢測,由於當多個線程執行第一次檢測經過後會同時進入同步快,那麼此時就有必要進行第二次檢測來避免生成多個實例。多線程
public class Singleton {
private static Singleton instance;
private Singleton() {
System.out.println("初始化");
}
public static Singleton getInstance() {
if(instance==null){
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}複製代碼
對於上面的代碼時近乎完美的,既然說近乎完美,那確定仍是有瑕疵的,瑕疵出現的緣由就是instance = new Singleton();這一句代碼,你可能會問,這會有什麼問題,其實我也不知道,哈哈。在計算機語言中,初始化包含了三個步驟併發
因爲java編譯器爲了儘量減小內存操做速度遠慢於CPU運行速度所帶來的CPU空置的影響,虛擬機會按照本身的一些規則(這規則後面再敘述)將程序編寫順序打亂——即寫在後面的代碼在時間順序上可能會先執行,而寫在前面的代碼會後執行——以儘量充分地利用CPU就會出現指令重排序(happen-before),從而致使上面的三個步驟執行順序發生改變。正常狀況下是123,可是若是指令重排後執行爲1,3,2那麼久會致使instance 爲空,進而致使程序出現問題。app
既然已經知道了上述雙重檢測機制會出現問題,那麼咱們該怎麼避免出現呢,該如何解決呢,最好的辦法就是不要使用,開個玩笑啦。java中有一個關鍵字volatile,他有一個做用就是防止指令重排序,那麼咱們把singleton用volatile修飾下就能夠了,以下。框架
private volatile static Singleton singleton;複製代碼
餓漢式與懶漢式區別是它在類加載的時候就進行初始化操做,而懶漢式是調用getInstance()方法時才進行初始化。
public class Singleton{
private static final Singleton instance = new Singleton();
private Singleton() {
System.out.println("初始化");
}
public static Singleton getInstance(){
return instance;
}
}複製代碼
與懶漢式相比,它是線程安全的(無需用同步關鍵字修飾),因爲沒有加鎖,執行效率也相對較高,可是也有一些缺點,在類加載時就初始化,會浪費內存。
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {
System.out.println("初始化");
}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}複製代碼
靜態內部類相對實現較爲簡單,而且它是一種懶加載機制, 當Singleton 類被裝載了,instance 不必定被初始化。由於 SingletonHolder 類沒有被主動使用,只有顯示經過調用 getInstance 方法時,纔會顯示裝載 SingletonHolder 類,從而實例化 instance。
好了,關於單例模式到這裏已經介紹完畢了。有問題歡迎留言指出,Have a wonderful day .