隨筆 - 71  文章 - 15  trackbacks - 0
          <2025年7月>
          293012345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          因為口渴,上帝創(chuàng)造了水;
          因為黑暗,上帝創(chuàng)造了火;
          因為我需要朋友,所以上帝讓你來到我身邊
          Click for Shaanxi xi'an, Shaanxi Forecast
          ╱◥█◣
            |田|田|
          ╬╬╬╬╬╬╬╬╬╬╬
          If only I have such a house!
          〖總在爬山 所以艱辛〗
          Email:myesjoy@yahoo.com.cn
          NickName:yesjoy
          MSN:myesjoy@hotmail.com
          QQ:150230516

          〖總在尋夢 所以苦痛〗

          常用鏈接

          留言簿(3)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          Hibernate在線

          Java友情

          Java認(rèn)證

          linux經(jīng)典

          OA系統(tǒng)

          Spring在線

          Structs在線

          專家專欄

          企業(yè)信息化

          大型設(shè)備共享系統(tǒng)

          工作流

          工作流產(chǎn)品

          網(wǎng)上購書

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          測試單例模式


          接下來,我使用與log4j相對應(yīng)的JUnit來測試單例類,它會貫穿在這篇文章余下的部分。如果你對JUnit或log4j不很熟悉,請參考相關(guān)資源。

          例2是一個用JUnit測試?yán)?的單例模式的案例:
          例2.一個單例模式的案例
          1. import org.apache.log4j.Logger;
          2. import junit.framework.Assert;
          3. import junit.framework.TestCase;
          4. public class SingletonTest extends TestCase {
          5.    private ClassicSingleton sone = null, stwo = null;
          6.    private static Logger logger = Logger.getRootLogger();
          7.    public SingletonTest(String name) {
          8.       super(name);
          9.    }
          10.    public void setUp() {
          11.       logger.info("getting singleton...");
          12.       sone = ClassicSingleton.getInstance();
          13.       logger.info("...got singleton: " + sone);
          14.       logger.info("getting singleton...");
          15.       stwo = ClassicSingleton.getInstance();
          16.       logger.info("...got singleton: " + stwo);
          17.    }
          18.    public void testUnique() {
          19.       logger.info("checking singletons for equality");
          20.       Assert.assertEquals(true, sone == stwo);
          21.    }
          22. }

          例2兩次調(diào)用ClassicSingleton.getInstance(),并且把返回的引用存儲在成員變量中。方法testUnique()會檢查這些引用看它們是否相同。例3是這個測試案例的輸出:
          例3.是這個測試案例的輸出
          1. Buildfile: build.xml
          2. init:
          3.      [echo] Build 20030414 (14-04-2003 03:08)
          4. compile:
          5. run-test-text:
          6.      [java] .INFO main: [b]getting singleton...[/b]
          7.      [java] INFO main: [b]created singleton:[/b] Singleton@e86f41
          8.      [java] INFO main: ...got singleton: Singleton@e86f41
          9.      [java] INFO main: [b]getting singleton...[/b]
          10.      [java] INFO main: ...got singleton: Singleton@e86f41
          11.      [java] INFO main: checking singletons for equality
          12.      [java] Time: 0.032
          13.      [java] OK (1 test)

          正如前面的清單所示,例2的簡單測試順利通過----通過ClassicSingleton.getInstance()獲得的兩個單例類的引用確實相同;然而,你要知道這些引用是在單線程中得到的。下面的部分著重于用多線程測試單例類。

          多線程因素的考慮


          在例1中的ClassicSingleton.getInstance()方法由于下面的代碼而不是線程安全的:
          1. 1: if(instance == null) {
          2. 2:    instance = new Singleton();
          3. 3: }

          如果一個線程在第二行的賦值語句發(fā)生之前切換,那么成員變量instance仍然是null,然后另一個線程可能接下來進入到if塊中。在這種情況下,兩個不同的單例類實例就被創(chuàng)建。不幸的是這種假定很少發(fā)生,這樣這種假定也很難在測試期間出現(xiàn)(譯注:在這可能是作者對很少出現(xiàn)這種情況而導(dǎo)致無法測試從而使人們放松警惕而感到嘆惜)。為了演示這個線程輪換,我得重新實現(xiàn)例1中的那個類。例4就是修訂后的單例類:
          例4.人為安排的方式
          1. import org.apache.log4j.Logger;
          2. public class Singleton {
          3.   private static Singleton singleton = null;
          4.   private static Logger logger = Logger.getRootLogger();
          5.   private static boolean firstThread = true;
          6.   protected Singleton() {
          7.     // Exists only to defeat instantiation.
          8.   }
          9.   public static Singleton getInstance() {
          10.      if(singleton == null) {
          11.         simulateRandomActivity();
          12.         singleton = new Singleton();
          13.      }
          14.      logger.info("created singleton: " + singleton);
          15.      return singleton;
          16.   }
          17.   private static void simulateRandomActivity() {
          18.      try {
          19.         if(firstThread) {
          20.            firstThread = false;
          21.            logger.info("sleeping...");
          22.            // This nap should give the second thread enough time
          23.            // to get by the first thread.
          24.              Thread.currentThread().sleep(50);
          25.        }
          26.      }
          27.      catch(InterruptedException ex) {
          28.         logger.warn("Sleep interrupted");
          29.      }
          30.   }
          31. }

          除了在這個清單中的單例類強制使用了一個多線程錯誤處理,例4類似于例1中的單例類。在getInstance()方法第一次被調(diào)用時,調(diào)用這個方法的線程會休眠50毫秒以便另外的線程也有時間調(diào)用getInstance()并創(chuàng)建一個新的單例類實例。當(dāng)休眠的線程覺醒時,它也會創(chuàng)建一個新的單例類實例,這樣我們就有兩個單例類實例。盡管例4是人為如此的,但它卻模擬了第一個線程調(diào)用了getInstance()并在沒有完成時被切換的真實情形。
          例5測試了例4的單例類:
          例5.失敗的測試
          1. import org.apache.log4j.Logger;
          2. import junit.framework.Assert;
          3. import junit.framework.TestCase;
          4. public class SingletonTest extends TestCase {
          5.    private static Logger logger = Logger.getRootLogger();
          6.    private static Singleton singleton = null;
          7.    public SingletonTest(String name) {
          8.       super(name);
          9.    }
          10.    public void setUp() {
          11.       singleton = null;
          12.    }
          13.    public void testUnique() throws InterruptedException {
          14.       // Both threads call Singleton.getInstance().
          15.       Thread threadOne = new Thread(new SingletonTestRunnable()),
          16.              threadTwo = new Thread(new SingletonTestRunnable());
          17.       threadOne.start();
          18.       threadTwo.start();
          19.       threadOne.join();
          20.       threadTwo.join();
          21.    }
          22.    private static class SingletonTestRunnable implements Runnable {
          23.       public void run() {
          24.          // Get a reference to the singleton.
          25.          Singleton s = Singleton.getInstance();
          26.          // Protect singleton member variable from
          27.          // multithreaded access.
          28.          synchronized(SingletonTest.class) {
          29.             if(singleton == null// If local reference is null...
          30.                singleton = s;     // ...set it to the singleton
          31.          }
          32.          // Local reference must be equal to the one and
          33.          // only instance of Singleton; otherwise, we have two
          34.                   // Singleton instances.
          35.          Assert.assertEquals(true, s == singleton);
          36.       }
          37.    }
          38. }

          例5的測試案例創(chuàng)建兩個線程,然后各自啟動,等待完成。這個案例保持了一個對單例類的靜態(tài)引用,每個線程都會調(diào)用Singleton.getInstance()。如果這個靜態(tài)成員變量沒有被設(shè)置,那么第一個線程就會將它設(shè)為通過調(diào)用getInstance()而得到的引用,然后這個靜態(tài)變量會與一個局部變量比較是否相等。
          在這個測試案例運行時會發(fā)生一系列的事情:第一個線程調(diào)用getInstance(),進入if塊,然后休眠;接著,第二個線程也調(diào)用getInstance()并且創(chuàng)建了一個單例類的實例。第二個線程會設(shè)置這個靜態(tài)成員變量為它所創(chuàng)建的引用。第二個線程檢查這個靜態(tài)成員變量與一個局部備份的相等性。然后測試通過。當(dāng)?shù)谝粋€線程覺醒時,它也會創(chuàng)建一個單例類的實例,并且它不會設(shè)置那個靜態(tài)成員變量(因為第二個線程已經(jīng)設(shè)置過了),所以那個靜態(tài)變量與那個局部變量脫離同步,相等性測試即告失敗。例6列出了例5的輸出:
          例6.例5的輸出
          1. Buildfile: build.xml
          2. init:
          3.      [echo] Build 20030414 (14-04-2003 03:06)
          4. compile:
          5. run-test-text:
          6. INFO Thread-1: sleeping...
          7. INFO Thread-2: created singleton: Singleton@7e5cbd
          8. INFO Thread-1: created singleton: Singleton@704ebb
          9. junit.framework.AssertionFailedError: expected: but was:
          10.    at junit.framework.Assert.fail(Assert.java:47)
          11.    at junit.framework.Assert.failNotEquals(Assert.java:282)
          12.    at junit.framework.Assert.assertEquals(Assert.java:64)
          13.    at junit.framework.Assert.assertEquals(Assert.java:149)
          14.    at junit.framework.Assert.assertEquals(Assert.java:155)
          15.    at SingletonTest$SingletonTestRunnable.run(Unknown Source)
          16.    at java.lang.Thread.run(Thread.java:554)
          17.      [java] .
          18.      [java] Time: 0.577
          19.      [java] OK (1 test)

          到現(xiàn)在為止我們已經(jīng)知道例4不是線程安全的,那就讓我們看看如何修正它。

          同步


          要使例4的單例類為線程安全的很容易----只要像下面一個同步化getInstance()方法:
          1. public synchronized static Singleton getInstance() {
          2.    if(singleton == null) {
          3.       simulateRandomActivity();
          4.       singleton = new Singleton();
          5.    }
          6.    logger.info("created singleton: " + singleton);
          7.    return singleton;
          8. }

          在同步化getInstance()方法后,我們就可以得到例5的測試案例返回的下面的結(jié)果:
          1. Buildfile: build.xml
          2. init:
          3.      [echo] Build 20030414 (14-04-2003 03:15)
          4. compile:
          5.     [javac] Compiling 2 source files
          6. run-test-text:
          7. INFO Thread-1: sleeping...
          8. INFO Thread-1: created singleton: Singleton@ef577d
          9. INFO Thread-2: created singleton: Singleton@ef577d
          10.      [java] .
          11.      [java] Time: 0.513
          12.      [java] OK (1 test)

          這此,這個測試案例工作正常,并且多線程的煩惱也被解決;然而,機敏的讀者可能會認(rèn)識到getInstance()方法只需要在第一次被調(diào)用時同步。因為同步的性能開銷很昂貴(同步方法比非同步方法能降低到100次左右),或許我們可以引入一種性能改進方法,它只同步單例類的getInstance()方法中的賦值語句。

          一種性能改進的方法


          尋找一種性能改進方法時,你可能會選擇像下面這樣重寫getInstance()方法:
          1. public static Singleton getInstance() {
          2.    if(singleton == null) {
          3.       synchronized(Singleton.class) { 
          4.          singleton = new Singleton();
          5.       }
          6.    }
          7.    return singleton;
          8. }

          這個代碼片段只同步了關(guān)鍵的代碼,而不是同步整個方法。然而這段代碼卻不是線程安全的。考慮一下下面的假定:線程1進入同步塊,并且在它給singleton成員變量賦值之前線程1被切換。接著另一個線程進入if塊。第二個線程將等待直到第一個線程完成,并且仍然會得到兩個不同的單例類實例。有修復(fù)這個問題的方法嗎?請讀下去。

          雙重加鎖檢查


          初看上去,雙重加鎖檢查似乎是一種使懶漢式實例化為線程安全的技術(shù)。下面的代碼片段展示了這種技術(shù):
          1. public static Singleton getInstance() {
          2.   if(singleton == null) {
          3.      synchronized(Singleton.class) {
          4.        if(singleton == null) {
          5.          singleton = new Singleton();
          6.        }
          7.     }
          8.   }
          9.   return singleton;
          10. }

          如果兩個線程同時訪問getInstance()方法會發(fā)生什么?想像一下線程1進行同步塊馬上又被切換。接著,第二個線程進入if 塊。當(dāng)線程1退出同步塊時,線程2會重新檢查看是否singleton實例仍然為null。因為線程1設(shè)置了singleton成員變量,所以線程2的第二次檢查會失敗,第二個單例類實例也就不會被創(chuàng)建。似乎就是如此。
          不幸的是,雙重加鎖檢查不會保證正常工作,因為編譯器會在Singleton的構(gòu)造方法被調(diào)用之前隨意給singleton賦一個值。如果在singleton引用被賦值之后而被初始化之前線程1被切換,線程2就會被返回一個對未初始化的單例類實例的引用。

          一個改進的線程安全的單例模式實現(xiàn)


          例7列出了一個簡單、快速而又是線程安全的單例模式實現(xiàn):
          例7.一個簡單的單例類
          1. public class Singleton {
          2.    public final static Singleton INSTANCE = new Singleton();
          3.    private Singleton() {
          4.          // Exists only to defeat instantiation.
          5.       }
          6. }

          這段代碼是線程安全的是因為靜態(tài)成員變量一定會在類被第一次訪問時被創(chuàng)建。你得到了一個自動使用了懶漢式實例化的線程安全的實現(xiàn);你應(yīng)該這樣使用它:
          1.       Singleton singleton = Singleton.INSTANCE;
          2.       singleton.dothis();
          3.       singleton.dothat();
          4.       ...

          當(dāng)然萬事并不完美,前面的Singleton只是一個折衷的方案;如果你使用那個實現(xiàn),你就無法改變它以便后來你可能想要允許多個單例類的實例。用一種更折哀的單例模式實現(xiàn)(通過一個getInstance()方法獲得實例)你可以改變這個方法以便返回一個唯一的實例或者是數(shù)百個實例中的一個.你不能用一個公開且是靜態(tài)的(public static)成員變量這樣做.

          你可以安全的使用例7的單例模式實現(xiàn)或者是例1的帶一個同步的getInstance()方法的實現(xiàn).然而,我們必須要研究另一個問題:你必須在編譯期指定這個單例類,這樣就不是很靈活.一個單例類的注冊表會讓我們在運行期指定一個單例類.
          posted on 2006-02-14 15:33 ★yesjoy★ 閱讀(234) 評論(0)  編輯  收藏 所屬分類: 設(shè)計模式
          主站蜘蛛池模板: 澄江县| 互助| 伊春市| 平武县| 澄江县| 霞浦县| 大厂| 武乡县| 双流县| 安远县| 桃江县| 榆林市| 福鼎市| 乐山市| 美姑县| 乐东| 阿合奇县| 苏尼特左旗| 石嘴山市| 泾阳县| 兰州市| 双桥区| 永吉县| 马边| 布拖县| 桐乡市| 克拉玛依市| 夏津县| 达拉特旗| 崇义县| 吉木乃县| 定南县| 鹤庆县| 吴桥县| 安塞县| 阿尔山市| 南阳市| 临武县| 瓮安县| 台北市| 张家川|