單例模式的一個疑問 – JAVA編程語言程序開發技術文章

網上很多關於單例模式寫法的文章,不外乎餓漢和懶漢兩種形式的討論。很多人喜歡用懶漢式,因為覺得它實現瞭延遲加載,可以讓系統的性能更好。但事實果真如此嗎?我對此存疑。

首先我們檢查一下餓漢和懶漢單例模式最簡單的寫法(這裡不討論哪種懶漢寫法更好):

// 餓漢
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() {
    }
}

這種寫法不但最簡潔,還能輕易擴展為實例數量固定的“多例模式”。

 

摘自 神奇好望角
 

發佈留言