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