最近看到組里有人實(shí)現(xiàn)單例模式喝滞,采用靜態(tài)內(nèi)部類的方式诫欠,不是很懂這種寫法的優(yōu)點(diǎn),查了一下各種寫法的優(yōu)缺點(diǎn)佣谐,總結(jié)一下。
內(nèi)容多處參考文章:http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/
懶漢式
public class Single {
private static Single mInstance;
private Single(){}
// 線程不安全
public static Single getInstance() {
if (mInstance == null) {
mInstance = new Single();
}
return mInstance;
}
// 線程安全方妖,效率低狭魂,只有一個線程能調(diào)用getInstance()方法。
public static synchronized Single getInstance() {
if (mInstance == null) {
mInstance = new Single();
}
return mInstance;
}
// 同步代碼塊加鎖吁断,雙重檢查鎖趁蕊。
public static Single getInstance() {
if (mInstance == null) { //Single Checked
synchronized (Single.class) {
if (mInstance == null) { //Double Checked
mInstance = new Single();
}
}
}
return mInstance ;
}
}
同步代碼塊加鎖,雙重檢查
- 這個是平時最常用的方式仔役,看似完美掷伙,其實(shí)是有問題的。
因?yàn)?code>mInstance = new Single();這句語句的執(zhí)行又兵,不是一個原子操作任柜,JVM在執(zhí)行這條語句時,做了3個操作沛厨。
- 給mInstance分配內(nèi)存宙地。
- 調(diào)用Single的構(gòu)造方法進(jìn)行初始化。
- 將mInstance對象指向分配的內(nèi)存空間(執(zhí)行完這步mInstance就非空啦)逆皮。
但是在 JVM 的即時編譯器中存在指令重排序的優(yōu)化宅粥。也就是說上面的第二步和第三步的順序是不能保證的,最終的執(zhí)行順序可能是 1-2-3 也可能是 1-3-2电谣。如果是后者秽梅,則在 3 執(zhí)行完畢、2 未執(zhí)行之前剿牺,被線程二搶占了企垦,這時 instance 已經(jīng)是非 null 了(但卻沒有初始化),所以線程二會直接返回 instance晒来,然后使用钞诡,然后順理成章地報錯。
餓漢式
public class Single {
// 類加載時就被初始化,線程安全
private static final Single mInstance = new Single();
private Single(){}
public static Single getInstance() {
return mInstance;
}
}
缺點(diǎn)
- 不是
懶加載
模式荧降,類被加載時就被初始化接箫。 - 如果構(gòu)造函數(shù)
需要傳遞參數(shù)
時,不能滿足誊抛。
靜態(tài)內(nèi)部類
public class Single {
private Single(){}
private static class InnerHolder {
private static final INSTANCE = new Single();
}
public static Single getInstance() {
return InnerHolder.INSTANCE;
}
}
這種寫法仍然使用JVM本身機(jī)制保證了線程安全問題列牺;由于 InnerHolder 是私有的,除了 getInstance() 之外沒有辦法訪問它拗窃,因此它是懶漢式的;同時讀取實(shí)例的時候不會進(jìn)行同步泌辫,沒有性能缺陷随夸;也不依賴 JDK 版本。
總結(jié)
單例模式
最好采用靜態(tài)內(nèi)部類
實(shí)現(xiàn)震放,但是如果對懶加載
和參數(shù)
沒有要求宾毒,餓漢式
也可以。