1. 懶漢式單例模式
通過(guò)延遲初始化,降低單例創(chuàng)建期間的資源開(kāi)銷疚沐。
懶漢式單例實(shí)現(xiàn)暂氯,存在線程安全問(wèn)題
public class LazySingleton {
private static LazySingleton lazySingleton;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (lazySingleton == null) { // 斷點(diǎn),suspend:thread
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
線程任務(wù)
@Slf4j
public class T implements Runnable {
@Override
public void run() {
LazySingleton instance = LazySingleton.getInstance();
log.info("{} - {}", Thread.currentThread(), instance);
}
}
在主線程中創(chuàng)建兩個(gè)線程任務(wù)T亮蛔,通過(guò)IDEA多線程Debug控制線程的執(zhí)行順序痴施,使其在控制臺(tái)輸出不同的實(shí)例。
private void singleton() {
Thread t1 = new Thread(new T());
Thread t2 = new Thread(new T());
t1.start();
t2.start();
log.info("end"); // 斷點(diǎn)究流,suspend:thread
}
備注:IDEA如何實(shí)現(xiàn)多線程Debug
斷點(diǎn)-右鍵斷點(diǎn)-掛起選擇thread模式
1.1. 使用synchronized
解決懶漢式線程安全問(wèn)題
public synchronized static LazySingleton getInstance() {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
這時(shí)候在使用多線程Debug辣吃,可以發(fā)現(xiàn)其中一個(gè)線程任務(wù)處于阻塞狀態(tài)。
這種方法性能開(kāi)銷較大芬探。
備注:synchronized鎖靜態(tài)方法相當(dāng)于鎖整個(gè)類文件神得,synchronized鎖普通方法相當(dāng)于鎖堆內(nèi)存中的對(duì)象實(shí)例。
1.2. 使用double check
雙重檢查解決懶漢式線程安全問(wèn)題
public class LazyDoubleCheckSingleton {
private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton;
private LazyDoubleCheckSingleton() {}
public static LazyDoubleCheckSingleton getInstance() {
if (lazyDoubleCheckSingleton == null) {
synchronized (LazyDoubleCheckSingleton.class) {
if (lazyDoubleCheckSingleton == null) {
lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
}
}
}
return lazyDoubleCheckSingleton;
}
}
存在指令重排序問(wèn)題偷仿,即lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
這行代碼實(shí)際上執(zhí)行了三個(gè)步驟:
- 分配堆內(nèi)存
- 初始化對(duì)象(類加載之后哩簿,被線程使用之前)
- 將棧中的變量指向堆內(nèi)存
2和3的執(zhí)行順序可能顛倒宵蕉,這就是指令重排序,在單線程環(huán)境下這個(gè)問(wèn)題不會(huì)影響到程序正常執(zhí)行卡骂,但是在多線程環(huán)境下国裳,線程1跳過(guò)了步驟2執(zhí)行了步驟3形入,這時(shí)候線程1掛起全跨,CPU控制權(quán)切換到線程2,線程2拿到還沒(méi)初始化的對(duì)象亿遂,程序就會(huì)拋異常浓若。
解決指令重排序問(wèn)題:
1)不允許指令重排序。使用volatile
private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton;
通過(guò)volatile
配合double check
雙重檢查的懶漢式單例模式不僅解決了線程安全問(wèn)題蛇数,而且還兼顧到了性能挪钓,這種方式會(huì)比直接使用synchronized
來(lái)得更好點(diǎn)。
2)允許指令重排序耳舅,但其它線程不可見(jiàn)碌上,通過(guò)靜態(tài)內(nèi)部類
解決。
public class StaticInnerClassSingleton {
private StaticInnerClassSingleton() {}
private static class InnerClass {
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return InnerClass.staticInnerClassSingleton;
}
}
Class對(duì)象的初始化鎖浦徊,即哪個(gè)線程拿到InnerClass對(duì)象的初始化鎖馏予,哪個(gè)對(duì)象就去初始化它,而其它線程屬于非構(gòu)造線程盔性,所以即使構(gòu)造期間出現(xiàn)了指令重排序霞丧,那么其它線程也是看不見(jiàn)的。
2. 餓漢式單例模式
使用final static
修飾實(shí)例對(duì)象使類在加載完成之后冕香,完成實(shí)例對(duì)象的初始化蛹尝,且不能再修改。
public class HungrySingleton {
private final static HungrySingleton hungrySingleton;
static {
hungrySingleton = new HungrySingleton();
}
private HungrySingleton() {}
public static HungrySingleton getInstance() {
return hungrySingleton;
}
}
這種方法使類加載完成之后就完成實(shí)例對(duì)象的初始化悉尾,若應(yīng)用里沒(méi)有使用到這個(gè)單例對(duì)象就會(huì)造成內(nèi)存資源的浪費(fèi)突那。與懶漢式不同,懶漢式是需要使用時(shí)才去初始化對(duì)象构眯,所以不用考慮資源浪費(fèi)問(wèn)題愕难。但是懶漢式存在線程安全問(wèn)題,而餓漢式則沒(méi)這種問(wèn)題鸵赖,開(kāi)發(fā)者可以根據(jù)實(shí)際的業(yè)務(wù)需求選擇最恰當(dāng)?shù)慕鉀Q方案务漩。
3. 破壞單例模式
序列化反序列化會(huì)破壞單例模式:
序列化和反序列化時(shí)獲取到的是兩個(gè)不同的實(shí)例對(duì)象,因?yàn)榉葱蛄谢瘯r(shí)它褪,會(huì)判斷單例類中是否存在readReslove
方法饵骨,如果有的話會(huì)調(diào)用這個(gè)方法返回單例對(duì)象,否則會(huì)通過(guò)反射機(jī)制重新獲取一個(gè)新的單例對(duì)象茫打。
反射攻擊也會(huì)破壞單例模式居触。
使用枚舉實(shí)現(xiàn)單例妖混,1)枚舉單例類不受序列化反序列化破壞,2)屏蔽反射攻擊