單例模式完全解析

          本文將探討單例模式的各種情況,并給出相應(yīng)的建議。單例模式應(yīng)該是設(shè)計(jì)模式中比較簡單的一個(gè),但是在多線程并發(fā)的環(huán)境下使用卻是不那么簡單了。
          首先看最原始的單例模式。

           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 


          顯然這個(gè)寫法在單線程環(huán)境下非常好,但是多線程會(huì)導(dǎo)致多個(gè)實(shí)例出現(xiàn),這個(gè)大家都能理解。
          最簡單的改造方式是添加一個(gè)同步鎖。

           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 


          顯然上面的方法避免了并發(fā)的問題,但是由于我們只是在第一次構(gòu)造對象的時(shí)候才需要同步,以后就不再需要同步,所以這里不可避免的有性能開銷。于是將鎖去掉采用靜態(tài)的屬性來解決同步鎖的問題。

           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 


          上面的方法既沒有鎖又解決了性能問題,看起來已經(jīng)滿足需求了。但是追求完美的程序員想延時(shí)加載對象,希望在第一次獲取的時(shí)候才構(gòu)造對象,于是大家非常聰明的進(jìn)行改造,也即非常出名的雙重檢查鎖機(jī)制出來了。

           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 


          雙重鎖機(jī)制看起來非常巧妙的避免了上面的問題。但是真的是這樣的嗎?文章《雙重檢查鎖定及單例模式》中談到了非常多演變的雙重鎖機(jī)制帶來的問題,包括比較難以理解的指令重排序機(jī)制等。總之就是雙重檢查鎖機(jī)制仍然對導(dǎo)致錯(cuò)誤問題而不是性能問題。
          于是繼續(xù)改造,某個(gè)牛人利用JVM的特性來解決上述問題,具體哪個(gè)牛人我忘記了,但是不是下面文章的作者。
          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 


          上述代碼看起來解決了上面單例模式遇到的所有問題,而且實(shí)際上工作的很好,沒有什么問題。但是卻有一個(gè)致命的問題,如果第11行拋出了一個(gè)異常,也就是第一次構(gòu)造函數(shù)失敗將導(dǎo)致永遠(yuǎn)無法再次得到構(gòu)建對象的機(jī)會(huì)。
          使用下面的代碼測試下。

           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一旦加載某個(gè)類失敗將認(rèn)為此類的定義有問題,將來不再加載,這樣就導(dǎo)致我們沒有機(jī)會(huì)再加載。目前看起來沒有辦法避免此問題。如果要使用JVM的類加載特性就必須保證類加載一定正確,否則此問題將比并發(fā)和性能更嚴(yán)重。如果我們的類需要初始話那么就需要想其它辦法避免在構(gòu)造函數(shù)中完成。看起來像是又回到了老地方,難道不是么?

          總之,結(jié)論是目前沒有一個(gè)十全十美的單例模式,而大多數(shù)情況下我們只需要滿足我們的需求就行,沒必有特意追求最完美解決方案。

          posted on 2010-06-15 17:39 愛運(yùn)動(dòng) 愛生活 閱讀(101) 評論(0)  編輯  收藏 所屬分類: 設(shè)計(jì)模式


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          <2025年6月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          導(dǎo)航

          統(tǒng)計(jì)

          公告

          好好學(xué)習(xí) 天天向上

          常用鏈接

          留言簿

          文章分類

          文章檔案

          搜索

          積分與排名

          最新評論

          主站蜘蛛池模板: 台湾省| 炉霍县| 乐陵市| 舟曲县| 道孚县| 那坡县| 历史| 澄迈县| 五常市| 临沭县| 高陵县| 神木县| 察隅县| 岐山县| 龙陵县| 金川县| 醴陵市| 德化县| 宁武县| 朝阳市| 通化市| 霸州市| 洛扎县| 开平市| 安溪县| 长武县| 上犹县| 佛坪县| 莱阳市| 云龙县| 蚌埠市| 晋宁县| 读书| 洪洞县| 广汉市| 新和县| 余姚市| 东宁县| 宿迁市| 新干县| 江山市|