通常Java實現(xiàn)單例模式有很多種方式涨颜,大致可分為懶漢模式
和餓漢模式
实蔽,其主要區(qū)別是實例延遲加載的問題上炎,當(dāng)然單例模式往往也關(guān)注其他問題寿冕,如:線程安全等蕊程。下面試圖來總結(jié)單例模式的這些注意點。
代碼地址:GitHub
餓漢模式
public class Singleton {
private Singleton(){}
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
餓漢模式在類加載時候就實例化對象驼唱,使用時直接調(diào)用getInstance()
方法藻茂。這個模式下,是線程安全的玫恳,在多線程并發(fā)模式下不會重復(fù)實例化對象辨赐。
缺點:對象過早的實例化,浪費系統(tǒng)資源京办。
懶漢模式
public class Singleton {
private Singleton(){}
private static Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
這種模式下在類加載時候并沒有實例化對象掀序,而是在調(diào)用getInstance()
方法。之所以使用懶漢模式惭婿,是為了避免多早的實例化對象不恭,從而浪費系統(tǒng)資源。
缺點:僅適用于單線程审孽,線程不安全县袱。
改進1 - 引入synchronized
public class Singleton {
private Singleton(){}
private static Singleton instance = null;
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
之所以引入synchronized
修飾getInstance()
方法,是為了解決線程不安全的問題佑力。利用多線程同步機制式散,讓原先的線程不安全回歸到線程安全。但引入synchronized
會因為線程阻塞打颤、切換會帶一些不必要的開銷暴拄,從而降低系統(tǒng)性能漓滔。
改進2 - 雙重檢查鎖定
public class Singleton {
private Singleton(){}
private static volatile Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) { // A
synchronized (Singleton.class) {
if (instance == null) { // B
instance = new Singleton(); // C
}
}
}
return instance;
}
}
對比改進1中,可以看到synchronized
不再修飾一個方法乖篷,而是縮減到修改代碼塊响驴,因為加鎖同步的話,范圍越小撕蔼,性能影響最小豁鲤。
這里可以注意到修飾變量instance
的關(guān)鍵字增加了volatile
。這里volatile
主要作用是提供內(nèi)存屏障鲸沮,禁止指令重排序琳骡。
現(xiàn)有t1、t2兩個線程同時訪問
getInstance()
,假設(shè)t1讼溺、t2都執(zhí)行到A處楣号。由于有同步鎖,只能有個1個線程獲得鎖怒坯,假如t1擁有該同步鎖炫狱,t1執(zhí)行到C處instace = new Singleton()
。將會做如下3步驟:
1.分配內(nèi)存空間
2.初始化
3.將instance指向分配內(nèi)存空間
正常的執(zhí)行順序應(yīng)為:1->2->3剔猿。執(zhí)行第3步時视译,這時候的instance
就不再是null了。但由于指令重排序的存在归敬,執(zhí)行順序有可能變化為:1->3->2憎亚。當(dāng)執(zhí)行3的時候,instance
就不再是null弄慰,但初始化工作有可能還沒有做完第美。這時候如果t2獲取鎖執(zhí)行的話,就會直接獲取有可能還沒有初始化完成的instance
陆爽。這樣使用instance
會引起程序報錯什往。當(dāng)然這也是極端情況下,我嘗試幾次無法捕捉重現(xiàn)慌闭,但并不意味著問題不存在别威。volatile
當(dāng)然還是要加的。
A處if
判斷作用主要是防止過多是線程執(zhí)行同步代碼塊驴剔;如果是單例模式的話省古,這里同步代碼塊只會被執(zhí)行一次。B處if
判斷作用主要是防止多線程作用下重復(fù)實例化丧失,保證線程安全豺妓。這也被稱為:雙重檢查鎖定。
雙重檢查鎖定屬于一種兼顧線程安全和性能的實現(xiàn)。
改進3 - 靜態(tài)內(nèi)部類
public class Singleton {
private Singleton(){}
private static class Holder {
public static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return Holder.instance; // 執(zhí)行Holder的初始化工作
}
}
使用靜態(tài)內(nèi)部類也是懶漢模式的一種實現(xiàn)琳拭,當(dāng)調(diào)用ggetInstance()
才會觸發(fā)加載靜態(tài)內(nèi)部類训堆,從而初始化獲取instance
實例。利用靜態(tài)內(nèi)部類的加載機制來保證線程安全白嘁。
枚舉方式
public enum Singleton {
INSTANCE;
Singleton(){}
public Singleton getInstance() {
return INSTANCE;
}
}
用枚舉方式實現(xiàn)單例模式坑鱼,是目前比較推薦的。枚舉方式的好處是:1絮缅、線程安全未状;2叁熔、防止反射出現(xiàn)多個實例喂链;3节沦、防止反序列化出現(xiàn)多個實例。
以上是關(guān)于java單例模式的一些總結(jié)屎开,如有紕漏,還請指出马靠。
代碼地址:GitHub