最近看到組里有人實現(xiàn)單例模式,采用靜態(tài)內(nèi)部類的方式,不是很懂這種寫法的優(yōu)點,查了一下各種寫法的優(yōu)缺點,總結(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 ;
}
}
同步代碼塊加鎖,雙重檢查
- 這個是平時最常用的方式,看似完美,其實是有問題的。
因為mInstance = new Single();這句語句的執(zhí)行,不是一個原子操作,JVM在執(zhí)行這條語句時,做了3個操作。
- 給mInstance分配內(nèi)存。
- 調(diào)用Single的構(gòu)造方法進行初始化。
- 將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;
}
}
缺點
- 不是
懶加載模式,類被加載時就被初始化。 - 如果構(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本身機制保證了線程安全問題;由于 InnerHolder 是私有的,除了 getInstance() 之外沒有辦法訪問它,因此它是懶漢式的;同時讀取實例的時候不會進行同步,沒有性能缺陷;也不依賴 JDK 版本。
總結(jié)
單例模式最好采用靜態(tài)內(nèi)部類實現(xiàn),但是如果對懶加載和參數(shù)沒有要求,餓漢式也可以。