深入理解單例模式的幾種實現方式
前言
單例模式是一種很常用的設計模式,其定義是單例物件的類只允許有一個例項存在。在使用spring自動建立物件時預設就是單例的。
使用場景
需要頻繁的對物件進行建立與銷燬,如果工具類物件
一、餓漢式(靜態變數)
public class Singleton1 { private static final Singleton1 INSTANCE = new Singleton1(); //靜態工廠 public static Singleton1 getInstance(){ return INSTANCE; } }
餓漢式就是在類載入初始化的時候就建立了物件,儘管你還不需要使用他。相對來說我是比較喜歡這種方式的,既沒有執行緒安全,速度也快,當然這是以犧牲空間換取速度,大家可以根據實際情況,根據該物件被建立的概率酌情使用該方法。
二、餓漢式(靜態程式碼塊)
public class Singleton2 { private static Singleton2 instance = null; //靜態程式碼塊 static { instance = new Singleton2(); } public Singleton2 getInstance(){ return instance; } }
靜態程式碼塊實現的餓漢式與上述第一種方式類似,都是在類初始化的時候就建立物件,同樣是執行緒安全的,其實就是JVM幫我們避免了執行緒安全,因為類只要載入一次,所以只會建立一個物件。
三、懶漢式(多執行緒下不可用)
public class Singleton3 { private static Singleton3 instance = null; public static Singleton3 getInstance(){ if (null == instance){ //(1) instance = new Singleton3(); //(2) } return instance; } }
這個懶漢式寫法是執行緒不安全的,只能在單執行緒情況下使用。假設有兩個執行緒A和B,執行緒A執行到(1)行程式碼時時間片已經完了,此時CPU切換執行執行緒B,執行緒B完成整個流程,也就是說這個時候instance已經不為空了,然後再執行執行緒A,此時執行緒A就會執行(2)行語句
四、懶漢式(多執行緒下可用,但效率低)
public class Singleton4 { private static Singleton4 instance = null; public static synchronized Singleton4 getInstance() { if (null == instance){ instance = new Singleton4(); } return instance; } }
第四個方法與第三個方法的區別在於在getInstance方法中加了synchronized同步鎖,這樣會導致只有等一個執行緒執行完後另一個執行緒才能進入該方法,當然不會有執行緒安全的問題,但我們只是在一開始例項化物件時需要,之後只需要直接返回物件即可,這樣相當於每次都要去查詢物件是否例項化,且還要排隊查詢,這樣顯然很影響效能,相當於每個執行緒都要排隊執行該方法。所以不推薦使用這種寫法。
五、靜態內部類
public class Singleton5 { private static class insideClass{ private static final Singleton5 instance = new Singleton5(); } public static Singleton5 getInstance(){ return insideClass.instance; } }
靜態內部類與懶漢式類似,只不過是JVM幫我們解決了多執行緒的問題。在呼叫getInstance()方法是會載入內部類insideClass,也就會例項化instance,ClassLoader類載入機制會幫我們保證只有一個執行緒去初始化該內部類,也就只會生成一個例項。看起來這個方法又是懶載入,又是執行緒安全好像很完美的樣子,但設計是要落地與業務的,沒有最好的寫法,只有最合適的寫法。假設建立這個物件花費的時間很長,這無疑會給初次呼叫getInstance()方法的使用者帶來不好的體驗,那麼這種寫法也是不可取的。
六、雙重檢查
public class Singleton6 { private static volatile Singleton6 instance = null; //(1) private Singleton6(){} public static Singleton6 getInstance(){ if (null == instance){ synchronized (Singleton6.class){ if (null == instance){ instance = new Singleton6(); } } } return instance; } }
Double-Check在多執行緒程式設計中是很常見的。通過兩次檢查和靜態程式碼塊來限制同一時間只有個執行緒在例項化物件,進而控制只有一個例項產生。看起來貌似很完美,但大家有沒有看到(1)行中的volatile關鍵字呢?他又有什麼作用呢?
雙重檢查的寫法在JDK1.5是有問題的,因為JVM優化會對指令進行重排序。我們理想中建立一個物件的過程是:a->b->c
a.JVM分配記憶體空間
b.初始化物件
c.將instance指向分配的記憶體空間
但實際上執行的順序可能是 a->c->b
假設有執行緒A和執行緒B,執行緒A執行new Singleton6(),指令執行的順序是a->c->b,此時只是將instance執行分配的記憶體空間,也就是說instance此時不等於null,這個時候執行緒B獲取instance例項去執行操作,但實際上對應的記憶體空間還沒有初始化,那麼將會出現錯誤。