單例模式算是設(shè)計模式中最容易理解,也是最容易手寫代碼的模式,但是其中涉及的知識點卻一點也不少,所以經(jīng)常作為面試題來考。一般單例都是五種寫法:懶漢,餓漢,雙重校驗鎖,靜態(tài)內(nèi)部類和枚舉。為了記錄學(xué)習(xí)過程的過程,這里整理了幾種常見的單例寫法,
青銅5:(Lazy-loaded,但線程不安全)
當被問到要實現(xiàn)一個單例模式時,很多人的第一反應(yīng)是寫出如下的代碼,包括教科書上也是這樣教我們的。
1
2
3
4
5
6
7
8
9
10
|
public class Singleton { private static Singleton instance; private Singleton(){} public static Singleton getInstance() { if (instance == null ) { instance = new Singleton(); } return instance; } } |
這段代碼簡單明了,而且使用了延遲加載模式,但是線程不安全。多線程環(huán)境下調(diào)用 getInstance() 方法,可能會發(fā)生多個線程進入if語句的程序代碼塊。
懶漢式:synchronized(Lazy-loaded,線程安全,但不高效)
為了解決上面的問題,最簡單的方法是將整個 getInstance() 方法設(shè)為同步(synchronized)。
1
2
3
4
5
6
7
8
9
10
|
public class Singleton { private static Singleton instance; private Singleton() {} public static synchronized Singleton getInstance() { if (instance == null ) { instance = new Singleton(); } return instance; } } |
雖然做到了線程安全、延遲加載,但是它并不高效。因為在任何時候只能有一個線程調(diào)用 getInstance() 方法。但是synchronized操作只需要在第一次調(diào)用時才被需要,即第一次創(chuàng)建單例實例對象時。這種模式導(dǎo)致即使在單例創(chuàng)建完成后,每次依然只有一個線程可以訪問getInstance()方法,會導(dǎo)致潛在的性能問題。這就引出了雙重檢驗鎖。
餓漢式:static final field(非Lazy-loaded)
這種方法非常簡單,因為單例的實例被聲明成 static final,在第一次加載類到內(nèi)存中時就會初始化,所以創(chuàng)建實例對象是線程安全的(由JVM實現(xiàn)保證)。
1
2
3
4
5
6
7
8
|
public class Singleton{ //類加載時就初始化 private static final Singleton instance = new Singleton(); private Singleton(){} public static Singleton getInstance(){ // Singleton with static factory return instance; } } |
它不是一種懶加載模式,instance會在加載類后一開始就被初始化,即使客戶端沒有調(diào)用 getInstance()方法。這會導(dǎo)致一些使用限制:譬如 Singleton 實例的創(chuàng)建是依賴參數(shù)或者配置文件的,在 getInstance() 之前必須調(diào)用某個方法設(shè)置參數(shù)給它,那樣這種單例寫法就無法使用了。類似的方法還有:
1
2
3
4
5
|
public class Singleton{ public static final Singleton instance = new Singleton(); // Singleton with public final field private Singleton(){} } // <Effective Java>第14頁講訴了兩者的差異 |
雙重檢驗鎖 + volatile(Lazyload,線程安全,但晦澀)
雙重檢驗鎖模式(double checked locking pattern),是一種使用同步塊加鎖的方法。程序員稱其為雙重檢查鎖,因為會有兩次檢查 instance == null,一次是在同步塊外,一次是在同步塊內(nèi)。為什么在同步塊內(nèi)還要再檢驗一次?因為可能會有多個線程一起進入同步塊外的 if,如果在同步塊內(nèi)不進行二次檢驗的話就會生成多個實例對象了。
1
2
3
4
5
6
7
8
9
10
|
public static Singleton getSingleton() { if (instance == null ) { //Single Checked synchronized (Singleton. class ) { if (instance == null ) { //Double Checked instance = new Singleton(); } } } return instance ; } |
這段代碼看起來很完美,很可惜它是有問題的。主要在于instance = new Singleton()這句,這并非是一個原子操作,事實上在 JVM 中這句話大概做了下面 3 件事情。
- 給 instance 分配內(nèi)存
- 調(diào)用 Singleton 的構(gòu)造函數(shù)來初始化成員變量
- 將instance對象指向分配的內(nèi)存空間(執(zhí)行完這步 instance 就為非 null 了)
但是在 JVM 的JIT編譯器中存在指令重排序的優(yōu)化。也就是說上面的第二步和第三步的順序是不能保證的,最終的執(zhí)行順序可能是 1-2-3 也可能是 1-3-2。如果是后者,則在 3 執(zhí)行完畢、2未執(zhí)行之前,被線程二搶占了,這時 instance 已經(jīng)是非 null 了(但卻沒有初始化),所以線程二會直接返回 instance,然后使用,然后順理成章地報錯。為此,我們需要將 instance 變量聲明成 volatile 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class Singleton { private volatile static Singleton instance; //聲明為 volatile private Singleton(){} public static Singleton getSingleton() { if (instance == null ) { synchronized (Singleton. class ) { if (instance == null ) { instance = new Singleton(); } } } return instance; } } |
但是特別注意在 Java 1.5 以前的版本使用了 volatile 的雙檢鎖還是有問題的,這個問題在 Java 1.5 中才得以修復(fù),所以在這之后才可以放心使用 volatile。
靜態(tài)內(nèi)部類:IoDH,initialization-on-demand holder
這個模式綜合使用了Java的靜態(tài)內(nèi)部類和多線程缺省同步鎖的知識,很巧妙地同時實現(xiàn)了延遲加載和線程安全。
1
2
3
4
5
6
7
8
9
|
public class Singleton { private Singleton() {} private static class LazyHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { // From wikipedia return LazyHolder.INSTANCE; } } |
靜態(tài)內(nèi)部類相當于其外部類的static部分,它的對象不依賴于外部類對象,因此可以直接創(chuàng)建。靜態(tài)內(nèi)部類只有在第一次被使用的時候才會被轉(zhuǎn)載。
多線程缺省同步鎖
大家都知道,在多線程開發(fā)中,為了解決并發(fā)問題,主要是通過使用synchronized來加互斥鎖進行同步控制。但是在某些情況中,JVM已經(jīng)隱含地為您執(zhí)行了同步,這些情況下就不需要手動進行同步控制了。這些情況包括:
1.由靜態(tài)初始化器(在靜態(tài)字段上或static{}塊中的初始化器)初始化數(shù)據(jù)時
2.訪問final字段時
3.在創(chuàng)建線程之前創(chuàng)建對象時
4.線程可以看見它將要處理的對象時
枚舉 Enum
從Java 1.5起,只需編寫一個包含單個元素的枚舉類型:
1
2
3
|
public enum Singleton { INSTANCE; } |
這種方法在功能上與公有域方法相近,但是它更加簡潔,無償?shù)靥峁┝诵蛄谢瘷C制,絕對防止多次實例化,即使是在面向復(fù)雜的序列化或者反射攻擊的時候。雖然這種方法還沒有廣泛采用,但是單元素的枚舉類型以及成為實現(xiàn)Singleton的最佳方法。
--------------------以下是幾個細節(jié)存疑的實現(xiàn)方法---------------------
1.static final到底有哪些細節(jié)
2.static field處的賦值初始化到底和static代碼塊有先后嗎?
3.靜態(tài)內(nèi)部類的單例模式到底怎么寫
4.P50 in Java EE設(shè)計模式解析與應(yīng)用中的例子真的有Lazyload效果嗎?
以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,同時也希望多多支持服務(wù)器之家!
原文鏈接:http://www.cnblogs.com/echo1937/p/6247050.html