最近看到組里有人實現單例模式,采用靜態內部類的方式,不是很懂這種寫法的優點,查了一下各種寫法的優缺點,總結一下。
內容多處參考文章: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;
}
// 線程安全,效率低,只有一個線程能調用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();
這句語句的執行,不是一個原子操作,JVM在執行這條語句時,做了3個操作。
- 給mInstance分配內存。
- 調用Single的構造方法進行初始化。
- 將mInstance對象指向分配的內存空間(執行完這步mInstance就非空啦)。
但是在 JVM 的即時編譯器中存在指令重排序的優化。也就是說上面的第二步和第三步的順序是不能保證的,最終的執行順序可能是 1-2-3 也可能是 1-3-2。如果是后者,則在 3 執行完畢、2 未執行之前,被線程二搶占了,這時 instance 已經是非 null 了(但卻沒有初始化),所以線程二會直接返回 instance,然后使用,然后順理成章地報錯。
餓漢式
public class Single {
// 類加載時就被初始化,線程安全
private static final Single mInstance = new Single();
private Single(){}
public static Single getInstance() {
return mInstance;
}
}
缺點
- 不是
懶加載
模式,類被加載時就被初始化。 - 如果構造函數
需要傳遞參數
時,不能滿足。
靜態內部類
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 版本。
總結
單例模式
最好采用靜態內部類
實現,但是如果對懶加載
和參數
沒有要求,餓漢式
也可以。