彩虹天堂
          技術源于生活
          posts - 0,  comments - 2,  trackbacks - 0
          多線程因素的考慮

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


          如果一個線程在第二行的賦值語句發生之前切換,那么成員變量instance仍然是null,然后另一個線程可能接下來進入到if塊中。在這種情況下,兩個不同的單例類實例就被創建。不幸的是這種假定很少發生,這樣這種假定也很難在測試期間出現(譯注:在這可能是作者對很少出現這種情況而導致無法測試從而使人們放松警惕而感到嘆惜)。為了演示這個線程輪換,我得重新實現例1中的那個類。例4就是修訂后的單例類:
          例4.人為安排的方式

          Java代碼 復制代碼
          1. import org.apache.log4j.Logger;    
          2.      
          3. public class Singleton {    
          4.   private static Singleton singleton = null;    
          5.   private static Logger logger = Logger.getRootLogger();    
          6.   private static boolean firstThread = true;    
          7.      
          8.   protected Singleton() {    
          9.     // Exists only to defeat instantiation.    
          10.   }    
          11.   public static Singleton getInstance() {    
          12.      if(singleton == null) {    
          13.         simulateRandomActivity();    
          14.         singleton = new Singleton();    
          15.      }    
          16.      logger.info("created singleton: " + singleton);    
          17.      return singleton;    
          18.   }    
          19.   private static void simulateRandomActivity() {    
          20.      try {    
          21.         if(firstThread) {    
          22.            firstThread = false;    
          23.            logger.info("sleeping...");    
          24.      
          25.            // This nap should give the second thread enough time    
          26.            // to get by the first thread.    
          27.              Thread.currentThread().sleep(50);    
          28.        }    
          29.      }    
          30.      catch(InterruptedException ex) {    
          31.         logger.warn("Sleep interrupted");    
          32.      }    
          33.   }    
          34. }   


          除了在這個清單中的單例類強制使用了一個多線程錯誤處理,例4類似于例1中的單例類。在getInstance()方法第一次被調用時,調用這個方法的線程會休眠50毫秒以便另外的線程也有時間調用getInstance()并創建一個新的單例類實例。當休眠的線程覺醒時,它也會創建一個新的單例類實例,這樣我們就有兩個單例類實例。盡管例4是人為如此的,但它卻模擬了第一個線程調用了getInstance()并在沒有完成時被切換的真實情形。
          例5測試了例4的單例類:
          例5.失敗的測試

          Java代碼 復制代碼
          1. import org.apache.log4j.Logger;    
          2. import junit.framework.Assert;    
          3. import junit.framework.TestCase;    
          4.      
          5. public class SingletonTest extends TestCase {    
          6.    private static Logger logger = Logger.getRootLogger();    
          7.    private static Singleton singleton = null;    
          8.      
          9.    public SingletonTest(String name) {    
          10.       super(name);    
          11.    }    
          12.    public void setUp() {    
          13.       singleton = null;    
          14.    }    
          15.    public void testUnique() throws InterruptedException {    
          16.       // Both threads call Singleton.getInstance().    
          17.       Thread threadOne = new Thread(new SingletonTestRunnable()),    
          18.              threadTwo = new Thread(new SingletonTestRunnable());    
          19.      
          20.       threadOne.start();    
          21.       threadTwo.start();    
          22.      
          23.       threadOne.join();    
          24.       threadTwo.join();    
          25.    }    
          26.    private static class SingletonTestRunnable implements Runnable {    
          27.       public void run() {    
          28.          // Get a reference to the singleton.    
          29.          Singleton s = Singleton.getInstance();    
          30.      
          31.          // Protect singleton member variable from    
          32.          // multithreaded access.    
          33.          synchronized(SingletonTest.class) {    
          34.             if(singleton == null// If local reference is null...    
          35.                singleton = s;     // ...set it to the singleton    
          36.          }    
          37.          // Local reference must be equal to the one and    
          38.          // only instance of Singleton; otherwise, we have two    
          39.                   // Singleton instances.    
          40.          Assert.assertEquals(true, s == singleton);    
          41.       }    
          42.    }    
          43. }   


          例5的測試案例創建兩個線程,然后各自啟動,等待完成。這個案例保持了一個對單例類的靜態引用,每個線程都會調用Singleton.getInstance()。如果這個靜態成員變量沒有被設置,那么第一個線程就會將它設為通過調用getInstance()而得到的引用,然后這個靜態變量會與一個局部變量比較是否相等。
          在這個測試案例運行時會發生一系列的事情:第一個線程調用getInstance(),進入if塊,然后休眠;接著,第二個線程也調用getInstance()并且創建了一個單例類的實例。第二個線程會設置這個靜態成員變量為它所創建的引用。第二個線程檢查這個靜態成員變量與一個局部備份的相等性。然后測試通過。當第一個線程覺醒時,它也會創建一個單例類的實例,并且它不會設置那個靜態成員變量(因為第二個線程已經設置過了),所以那個靜態變量與那個局部變量脫離同步,相等性測試即告失敗。例6列出了例5的輸出:
          例6.例5的輸出

          Java代碼 復制代碼
          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.      
          20.      [java] OK (1 test)   


          到現在為止我們已經知道例4不是線程安全的,那就讓我們看看如何修正它。


          同步

          要使例4的單例類為線程安全的很容易----只要像下面一個同步化getInstance()方法:
          Java代碼 復制代碼
          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的測試案例返回的下面的結果:
          Java代碼 復制代碼
          1. Buildfile: build.xml    
          2.      
          3. init:    
          4.      [echo] Build 20030414 (14-04-2003 03:15)    
          5.      
          6. compile:    
          7.     [javac] Compiling 2 source files    
          8.      
          9. run-test-text:    
          10. INFO Thread-1: sleeping...    
          11. INFO Thread-1: created singleton: Singleton@ef577d    
          12. INFO Thread-2: created singleton: Singleton@ef577d    
          13.      [java] .    
          14.      [java] Time: 0.513    
          15.      
          16.      [java] OK (1 test)   


          這此,這個測試案例工作正常,并且多線程的煩惱也被解決;然而,機敏的讀者可能會認識到getInstance()方法只需要在第一次被調用時同步。因為同步的性能開銷很昂貴(同步方法比非同步方法能降低到100次左右),或許我們可以引入一種性能改進方法,它只同步單例類的getInstance()方法中的賦值語句。
          posted on 2008-05-05 22:09 bcterry 閱讀(81) 評論(0)  編輯  收藏

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


          網站導航:
           

          <2025年7月>
          293012345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          留言簿

          文章檔案

          搜索

          •  

          最新評論

          主站蜘蛛池模板: 大田县| 乌审旗| 商都县| 新龙县| 珠海市| 铜陵市| 吉木萨尔县| 岫岩| 公安县| 宣威市| 承德县| 丰宁| 青冈县| 宁德市| 肇庆市| 那坡县| 陆川县| 炉霍县| 荥阳市| 分宜县| 伊春市| 曲周县| 五家渠市| 霍城县| 三原县| 定南县| 榆社县| 万源市| 萝北县| 堆龙德庆县| 永嘉县| 绵竹市| 德惠市| 丰都县| 屏东市| 木兰县| 广昌县| 焦作市| 十堰市| 琼海市| 上思县|