xylz,imxylz

          關注后端架構、中間件、分布式和并發編程

             :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
            111 隨筆 :: 10 文章 :: 2680 評論 :: 0 Trackbacks
          本文將探討單例模式的各種情況,并給出相應的建議。單例模式應該是設計模式中比較簡單的一個,但是在多線程并發的環境下使用卻是不那么簡單了。
          首先看最原始的單例模式。
          1 package xylz.study.singleton;
          2 
          3 public class Singleton {
          4 
          5     private static Singleton instance = null;
          6 
          7     private Singleton() {
          8     }
          9 
          10     public static Singleton getInstance() {
          11         if (instance == null) {
          12             instance = new Singleton();
          13         }
          14         return instance;
          15     }
          16 }
          17 

          顯然這個寫法在單線程環境下非常好,但是多線程會導致多個實例出現,這個大家都能理解。
          最簡單的改造方式是添加一個同步鎖。
          1 package xylz.study.singleton;
          2 
          3 public class SynchronizedSingleton {
          4 
          5     private static SynchronizedSingleton instance = null;
          6 
          7     private SynchronizedSingleton() {
          8     }
          9 
          10     public static synchronized SynchronizedSingleton getInstance() {
          11         if (instance == null) {
          12             instance = new SynchronizedSingleton();
          13         }
          14         return instance;
          15     }
          16 }
          17 

          顯然上面的方法避免了并發的問題,但是由于我們只是在第一次構造對象的時候才需要同步,以后就不再需要同步,所以這里不可避免的有性能開銷。于是將鎖去掉采用靜態的屬性來解決同步鎖的問題。
          1 package xylz.study.singleton;
          2 
          3 public class StaticSingleton {
          4 
          5     private static StaticSingleton instance = new StaticSingleton();
          6 
          7     private StaticSingleton() {
          8     }
          9 
          10     public static StaticSingleton getInstance() {
          11         return instance;
          12     }
          13 }
          14 

          上面的方法既沒有鎖又解決了性能問題,看起來已經滿足需求了。但是追求“完美”的程序員想延時加載對象,希望在第一次獲取的時候才構造對象,于是大家非常聰明的進行改造,也即非常出名的雙重檢查鎖機制出來了。
          1 package xylz.study.singleton;
          2 
          3 public class DoubleLockSingleton {
          4 
          5     private static DoubleLockSingleton instance = null;
          6 
          7     private DoubleLockSingleton() {
          8     }
          9 
          10     public static DoubleLockSingleton getInstance() {
          11         if (instance == null) {
          12             synchronized (DoubleLockSingleton.class) {
          13                 if (instance == null) {
          14                     instance = new DoubleLockSingleton();
          15                 }
          16             }
          17         }
          18         return instance;
          19     }
          20 }
          21 


          雙重鎖機制看起來非常巧妙的避免了上面的問題。但是真的是這樣的嗎?文章《雙重檢查鎖定及單例模式》中談到了非常多演變的雙重鎖機制帶來的問題,包括比較難以理解的指令重排序機制等。總之就是雙重檢查鎖機制仍然對導致錯誤問題而不是性能問題。

          一種避免上述問題的解決方案是使用volatile關鍵字,此關鍵字保證對一個對象修改后能夠立即被其它線程看到,也就是避免了指令重排序和可見性問題。參考文章

          指令重排序與happens-before法則

          所以上面的寫法就變成了下面的例子。

          package xylz.study.singleton;

          public class DoubleLockSingleton {

              private static volatile DoubleLockSingleton instance = null;

              private DoubleLockSingleton() {
              }

              public static DoubleLockSingleton getInstance() {
                  if (instance == null) {
                      synchronized (DoubleLockSingleton.class) {
                          if (instance == null) {
                              instance = new DoubleLockSingleton();
                          }
                      }
                  }
                  return instance;
              }
          }


          于是繼續改造,某個牛人利用JVM的特性來解決上述問題,具體哪個牛人我忘記了,但是不是下面文章的作者。
          (1)《Java theory and practice: Fixing the Java Memory Model, Part 2
          (2)《Initialize-On-Demand Holder Class and Singletons

          1 package xylz.study.singleton;
          2 
          3 public class HolderSingleton {
          4 
          5     private static class HolderSingletonHolder {
          6 
          7         static HolderSingleton instance = new HolderSingleton();
          8     }
          9 
          10     private HolderSingleton() {
          11         //maybe throw an Exception when doing something
          12     }
          13 
          14     public static HolderSingleton getInstance() {
          15         return HolderSingletonHolder.instance;
          16     }
          17 }
          18 




          上述代碼看起來解決了上面單例模式遇到的所有問題,而且實際上工作的很好,沒有什么問題。但是卻有一個致命的問題,如果第11行拋出了一個異常,也就是第一次構造函數失敗將導致永遠無法再次得到構建對象的機會。
          使用下面的代碼測試下。
          1 package xylz.study.singleton;
          2 
          3 public class HolderSingletonTest {
          4 
          5     private static class HolderSingletonHolder {
          6 
          7         static HolderSingletonTest instance = new HolderSingletonTest();
          8     }
          9 
          10     private static boolean init = false;
          11    
          12     private HolderSingletonTest() {
          13         //maybe throw an Exception when doing something
          14         if(!init) {
          15             init=true;
          16             throw new RuntimeException("fail");
          17         }
          18     }
          19 
          20     public static HolderSingletonTest getInstance() {
          21         return HolderSingletonHolder.instance;
          22     }
          23     public static void main(String[] args) {
          24         for(int i=0;i<3;i++) {
          25             try {
          26                 System.out.println(HolderSingletonTest.getInstance());
          27             } catch (Exception e) {
          28                 System.err.println("one->"+i);
          29                 e.printStackTrace();
          30             }catch(ExceptionInInitializerError err) {
          31                 System.err.println("two->"+i);
          32                 err.printStackTrace();
          33             }catch(Throwable t) {
          34                 System.err.println("three->"+i);
          35                 t.printStackTrace();
          36             }
          37         }
          38     }
          39 }
          40 
          很不幸將得到以下輸出:
          1 two->0
          2 java.lang.ExceptionInInitializerError
          3     at xylz.study.singleton.HolderSingletonTest.getInstance(HolderSingletonTest.java:21)
          4     at xylz.study.singleton.HolderSingletonTest.main(HolderSingletonTest.java:26)
          5 Caused by: java.lang.RuntimeException: fail
          6     at xylz.study.singleton.HolderSingletonTest.<init>(HolderSingletonTest.java:16)
          7     at xylz.study.singleton.HolderSingletonTest.<init>(HolderSingletonTest.java:12)
          8     at xylz.study.singleton.HolderSingletonTest$HolderSingletonHolder.<clinit>(HolderSingletonTest.java:7)
          9      2 more
          10 three->1
          11 java.lang.NoClassDefFoundError: Could not initialize class xylz.study.singleton.HolderSingletonTest$HolderSingletonHolder
          12     at xylz.study.singleton.HolderSingletonTest.getInstance(HolderSingletonTest.java:21)
          13     at xylz.study.singleton.HolderSingletonTest.main(HolderSingletonTest.java:26)
          14 three->2
          15 java.lang.NoClassDefFoundError: Could not initialize class xylz.study.singleton.HolderSingletonTest$HolderSingletonHolder
          16     at xylz.study.singleton.HolderSingletonTest.getInstance(HolderSingletonTest.java:21)
          17     at xylz.study.singleton.HolderSingletonTest.main(HolderSingletonTest.java:26)
          18 

          很顯然我們想著第一次加載失敗第二次能夠加載成功,非常不幸,JVM一旦加載某個類失敗將認為此類的定義有問題,將來不再加載,這樣就導致我們沒有機會再加載。目前看起來沒有辦法避免此問題。如果要使用JVM的類加載特性就必須保證類加載一定正確,否則此問題將比并發和性能更嚴重。如果我們的類需要初始話那么就需要想其它辦法避免在構造函數中完成。看起來像是又回到了老地方,難道不是么?

          總之,結論是目前沒有一個十全十美的單例模式,而大多數情況下我們只需要滿足我們的需求就行,沒必有特意追求最“完美”解決方案。
          原文[http://www.imxylz.info/p/177.html]

          ©2009-2014 IMXYLZ |求賢若渴
          posted on 2009-12-18 23:15 imxylz 閱讀(7352) 評論(4)  編輯  收藏 所屬分類: J2EE技術

          評論

          # re: 單例模式完全解析 2011-12-06 14:49 漆黑矢牙
          這么鼓搗下去直接崩潰了。。
          雙重鎖妥妥的。  回復  更多評論
            

          # re: 單例模式完全解析 2012-10-22 16:00 jmstt
          像 這樣搞下去, 直接吐血而亡了, 平時寫的代碼一大半都是錯誤的,  回復  更多評論
            

          # re: 單例模式完全解析 2012-10-22 16:03 jmstt
          《雙重檢查鎖定及單例模式》 開始看著還挺靠譜的, 后來就受不了了, 跟唐僧一樣, 這是不對的, 那樣也是不對的, 瘋了。。。。這樣搞java就是一個廢物了  回復  更多評論
            

          # re: 單例模式完全解析[未登錄] 2012-12-20 16:18 jay
          @jmstt
          不是不能在多線程條件下使用單例模式,而是沒有完美的方式。
          只要getInstance()加上synchronized就可以實現。并且這個是只存在于1.6之前的問題。

          1.6之后使用volatile就可以避免【指令重排】問題,《雙重檢查鎖定及單例模式》就可以實現了。

          -java就是一個廢物了,這樣的話在沒有深入了解之前,不要亂說啊。  回復  更多評論
            


          ©2009-2014 IMXYLZ
          主站蜘蛛池模板: 茶陵县| 通海县| 独山县| 山阳县| 曲阳县| 密云县| 上饶县| 奇台县| 永春县| 临夏县| 永济市| 桦南县| 淮阳县| 山丹县| 车致| 万全县| 珲春市| 清镇市| 八宿县| 兴仁县| 琼结县| 萨嘎县| 连平县| 桑日县| 上林县| 金门县| 永修县| 新绛县| 芷江| 庆城县| 阜宁县| 安西县| 沙坪坝区| 龙江县| 拉萨市| 汉沽区| 汝城县| 漳州市| 合江县| 安多县| 涿鹿县|