網(wǎng)上很多關(guān)于單例模式寫法的文章,不外乎餓漢和懶漢兩種形式的討論。很多人喜歡用懶漢式,因為覺得它實現(xiàn)了延遲加載,可以讓系統(tǒng)的性能更好。但事實果真如此嗎?我對此存疑。
首先我們檢查一下餓漢和懶漢單例模式最簡單的寫法(這里不討論哪種懶漢寫法更好):
// 餓漢
public final class HungrySingleton {
private static final HungrySingleton INSTANCE = new HungrySingleton();
private HungrySingleton() {
System.out.println("Initializing...");
}
public static HungrySingleton getInstance() {
return INSTANCE;
}
}
// 懶漢
public final class LazySingleton {
private static LazySingleton INSTANCE;
private LazySingleton() {
System.out.println("Initializing...");
}
public static synchronized LazySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new LazySingleton();
}
return INSTANCE;
}
}
從理論上來說,HungrySingleton
的單例在該類第一次使用的時候創(chuàng)建,而 LazySingleton
的單例則在其 getInstance()
方法被調(diào)用的時候創(chuàng)建。至于網(wǎng)上有人聲稱“餓漢式不管用不用都會初始化”,純屬走路的時候步子邁得太大。誰的加載更遲?如果你只是調(diào)用它們的 getInstance()
方法來得到單例對象,則它們都是延遲加載,這樣懶漢式?jīng)]有任何意義,而且由于 LazySingleton
采取了同步措施,性能更低(可以說任何懶漢式的性能都低于餓漢式)。當你使用一個單例類的時候,難道第一步不是調(diào)用 getInstance()
么?所以在自己的代碼里,我更喜歡用餓漢式。
下面用一個例子來測試加載順序:
// 餓漢
System.out.println("Before");
HungrySingleton.getInstance();
System.out.println("After");
// 懶漢
System.out.println("Before");
LazySingleton.getInstance();
System.out.println("After");
輸出結(jié)果都是:
Before
Initializing...
After
那么,懶漢模式還有什么存在意義?如果系統(tǒng)使用了某些需要在啟動時對類進行掃描的框架,使用餓漢式的話,啟動時間比懶漢式更長,如果使用了大量單例類,不利于開發(fā)階段。在系統(tǒng)的正式運行階段,所有的單例類遲早都要加載的,總的說來兩者性能持平,但是懶漢式每次都至少多一個判斷,所以越到后期越體現(xiàn)餓漢的優(yōu)越性。
最后,推薦下《Effective Java》第二版指出的用枚舉類型實現(xiàn)的餓漢單例模式:
// 餓漢
public enum HungrySingleton {
INSTANCE;
private HungrySingleton() {
}
}
這種寫法不但最簡潔,還能輕易擴展為實例數(shù)量固定的“多例模式”。