神奇好望角 The Magical Cape of Good Hope

          庸人不必自擾,智者何需千慮?
          posts - 26, comments - 50, trackbacks - 0, articles - 11
            BlogJava :: 首頁 ::  :: 聯系 :: 聚合  :: 管理

          單例模式的一個疑問

          Posted on 2012-01-10 17:39 蜀山兆孨龘 閱讀(1848) 評論(6)  編輯  收藏 所屬分類: Java SE

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

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

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

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


          評論

          # re: 單例模式的一個疑問[未登錄]  回復  更多評論   

          2012-01-11 08:33 by test
          public enum HungrySingleton 不會延遲加載

          # re: 單例模式的一個疑問  回復  更多評論   

          2012-01-11 10:15 by 蜀山兆孨龘
          @test
          只有在第一次用 HungrySingleton 的時候才會加載其單例對象。如果代碼里面不使用這個枚舉,根本不會加載。這還不叫延遲加載?

          # re: 單例模式的一個疑問  回復  更多評論   

          2012-01-11 11:45 by Sakura
          enum是用了java的語法特性

          我們開發組用單例模式時,將實例緩存下來:
          private ModelLocator model = ModelLocator.getInstance();
          每次用時都直接用model,而不是重復的調用getInstance()
          保持這種用法約定的話就達到了Lazy的優勢。
          對象只有第一次使用時才加載,加載后保存在private 成員變量中,
          以后每次用時不會再調用getInstance(),即不會參與多余的判斷和同步的性能消耗。
          如果不Lazy的話,采用框架啟動掃描類時會慢

          java的語法特性,完全可以用enum來實現非Lazy加載,代碼簡潔、可讀性高

          # re: 單例模式的一個疑問  回復  更多評論   

          2012-01-11 13:21 by 蜀山兆孨龘
          @Sakura
          你這種寫法確實達到了延遲的效果,但我并不覺得它有什么大的優勢,除非在你的系統中有相當一部分單例類很難被調用——這幾乎不可能,早晚都會全部加載。

          系統的啟動時間長一點沒關系,在運行的時候時不時卡一下(延遲加載單例)就不太友好了。

          # re: 單例模式的一個疑問[未登錄]  回復  更多評論   

          2012-01-12 09:28 by test
          加載HungrySingleton類的時候就會實例化,這個不叫延遲加載
          加載類的時候不實例化而是真正用到實例的時候實例化才叫延遲加載

          # re: 單例模式的一個疑問  回復  更多評論   

          2012-01-12 10:24 by 蜀山兆孨龘
          @test
          當你使用一個單例類的時候,第一步不是調用 getInstance() 嗎?莫非你還在單例類里面寫了不少靜態工具方法?當然,除非你正在做一個會對類進行掃描的框架。
          主站蜘蛛池模板: 洞头县| 云梦县| 镇巴县| 曲靖市| 商丘市| 龙里县| 吉安县| 托克逊县| 兴隆县| 哈密市| 集安市| 潢川县| 巢湖市| 南和县| 迁安市| 二手房| 莱芜市| 瑞昌市| 山阴县| 得荣县| 成武县| 涞水县| 苍溪县| 长丰县| 怀柔区| 个旧市| 承德县| 永兴县| 广宗县| 临清市| 浪卡子县| 长春市| 镇远县| 房产| 富阳市| 辛集市| 军事| 昌江| 青神县| 新丰县| 界首市|