<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    CONAN ZONE

    你越掙扎我就越興奮

    BlogJava 首頁(yè) 新隨筆 聯(lián)系 聚合 管理
      0 Posts :: 282 Stories :: 0 Comments :: 0 Trackbacks
    概要
    單例模式是最簡(jiǎn)單的設(shè)計(jì)模式之一,但是對(duì)于Java的開(kāi)發(fā)者來(lái)說(shuō),它卻有很多缺陷。在本月的專(zhuān)欄中,David Geary探討了單例模式以及在面對(duì)多線程(multithreading)、類(lèi)裝載器(classloaders)和序列化 (serialization)時(shí)如何處理這些缺陷。

    單例模式適合于一個(gè)類(lèi)只有一個(gè)實(shí)例的情況,比如窗口管理器,打印緩沖池和文件系統(tǒng),它們都是原型的例子。典型的情況是,那些對(duì)象的類(lèi)型被遍及一個(gè)軟件系統(tǒng)的不同對(duì)象訪問(wèn),因此需要一個(gè)全局的訪問(wèn)指針,這便是眾所周知的單例模式的應(yīng)用。當(dāng)然這只有在你確信你不再需要任何多于一個(gè)的實(shí)例的情況下。
    單例模式的用意在于前一段中所關(guān)心的。通過(guò)單例模式你可以:


    確保一個(gè)類(lèi)只有一個(gè)實(shí)例被建立
    提供了一個(gè)對(duì)對(duì)象的全局訪問(wèn)指針
    在不影響單例類(lèi)的客戶(hù)端的情況下允許將來(lái)有多個(gè)實(shí)例

    盡管單例設(shè)計(jì)模式如在下面的圖中的所顯示的一樣是最簡(jiǎn)單的設(shè)計(jì)模式,但對(duì)于粗心的Java開(kāi)發(fā)者來(lái)說(shuō)卻呈現(xiàn)出許多缺陷。這篇文章討論了單例模式并揭示了那些缺陷。
    注意:你可以從Resources下載這篇文章的源代碼。

    單例模式

    在《設(shè)計(jì)模式》一書(shū)中,作者這樣來(lái)敘述單例模式的:確保一個(gè)類(lèi)只有一個(gè)實(shí)例并提供一個(gè)對(duì)它的全局訪問(wèn)指針。
    下圖說(shuō)明了單例模式的類(lèi)圖。
    (圖1)

    單例模式的類(lèi)圖

    正如你在上圖中所看到的,這不是單例模式的完整部分。此圖中單例類(lèi)保持了一個(gè)對(duì)唯一的單例實(shí)例的靜態(tài)引用,并且會(huì)從靜態(tài)getInstance()方法中返回對(duì)那個(gè)實(shí)例的引用。
    例1顯示了一個(gè)經(jīng)典的單例模式的實(shí)現(xiàn)。
    例1.經(jīng)典的單例模式

     1public class ClassicSingleton 
     2   private static ClassicSingleton instance = null
     3  
     4   protected ClassicSingleton() 
     5      // Exists only to defeat instantiation. 
     6   }
     
     7   public static ClassicSingleton getInstance() 
     8      if(instance == null
     9         instance = new ClassicSingleton(); 
    10      }
     
    11      return instance; 
    12   }
     
    13}
     
    14

    在例1中的單例模式的實(shí)現(xiàn)很容易理解。ClassicSingleton類(lèi)保持了一個(gè)對(duì)單獨(dú)的單例實(shí)例的靜態(tài)引用,并且從靜態(tài)方法getInstance()中返回那個(gè)引用。
    關(guān)于ClassicSingleton類(lèi),有幾個(gè)讓我們感興趣的地方。首先,ClassicSingleton使用了一個(gè)眾所周知的懶漢式實(shí)例化去創(chuàng)建那個(gè)單例類(lèi)的引用;結(jié)果,這個(gè)單例類(lèi)的實(shí)例直到getInstance()方法被第一次調(diào)用時(shí)才被創(chuàng)建。這種技巧可以確保單例類(lèi)的實(shí)例只有在需要時(shí)才被建立出來(lái)。其次,注意ClassicSingleton實(shí)現(xiàn)了一個(gè)protected的構(gòu)造方法,這樣客戶(hù)端不能直接實(shí)例化一個(gè) ClassicSingleton類(lèi)的實(shí)例。然而,你會(huì)驚奇的發(fā)現(xiàn)下面的代碼完全合法:
    1public class SingletonInstantiator {  
    2  public SingletonInstantiator() {  
    3   ClassicSingleton instance = ClassicSingleton.getInstance(); 
    4ClassicSingleton anotherInstance = 
    5new ClassicSingleton(); 
    6         
    7  }
      
    8}
     
    9

    前面這個(gè)代碼片段為何能在沒(méi)有繼承ClassicSingleton并且ClassicSingleton類(lèi)的構(gòu)造方法是protected的情況下創(chuàng)建其實(shí)例?答案是protected的構(gòu)造方法可以被其子類(lèi)以及在同一個(gè)包中的其它類(lèi)調(diào)用。因?yàn)镃lassicSingleton和 SingletonInstantiator位于相同的包(缺省的包),所以SingletonInstantiator方法能創(chuàng)建 ClasicSingleton的實(shí)例。
    這種情況下有兩種解決方案:一是你可以使ClassicSingleton的構(gòu)造方法變化私有的(private)這樣只有 ClassicSingleton的方法能調(diào)用它;然而這也意味著ClassicSingleton不能有子類(lèi)。有時(shí)這是一種很合意的解決方法,如果確實(shí)如此,那聲明你的單例類(lèi)為final是一個(gè)好主意,這樣意圖明確,并且讓編譯器去使用一些性能優(yōu)化選項(xiàng)。另一種解決方法是把你的單例類(lèi)放到一個(gè)外在的包中,以便在其它包中的類(lèi)(包括缺省的包)無(wú)法實(shí)例化一個(gè)單例類(lèi)。
    關(guān)于ClassicSingleton的第三點(diǎn)感興趣的地方是,如果單例由不同的類(lèi)裝載器裝入,那便有可能存在多個(gè)單例類(lèi)的實(shí)例。假定不是遠(yuǎn)端存取,例如一些servlet容器對(duì)每個(gè)servlet使用完全不同的類(lèi)裝載器,這樣的話如果有兩個(gè)servlet訪問(wèn)一個(gè)單例類(lèi),它們就都會(huì)有各自的實(shí)例。
    第四點(diǎn),如果ClasicSingleton實(shí)現(xiàn)了java.io.Serializable接口,那么這個(gè)類(lèi)的實(shí)例就可能被序列化和復(fù)原。不管怎樣,如果你序列化一個(gè)單例類(lèi)的對(duì)象,接下來(lái)復(fù)原多個(gè)那個(gè)對(duì)象,那你就會(huì)有多個(gè)單例類(lèi)的實(shí)例。
    最后也許是最重要的一點(diǎn),就是例1中的ClassicSingleton類(lèi)不是線程安全的。如果兩個(gè)線程,我們稱(chēng)它們?yōu)榫€程1和線程2,在同一時(shí)間調(diào)用ClassicSingleton.getInstance()方法,如果線程1先進(jìn)入if塊,然后線程2進(jìn)行控制,那么就會(huì)有 ClassicSingleton的兩個(gè)的實(shí)例被創(chuàng)建。

    正如你從前面的討論中所看到的,盡管單例模式是最簡(jiǎn)單的設(shè)計(jì)模式之一,在Java中實(shí)現(xiàn)它也是決非想象的那么簡(jiǎn)單。這篇文章接下來(lái)會(huì)揭示Java規(guī)范對(duì)單例模式進(jìn)行的考慮,但是首先讓我們近水樓臺(tái)的看看你如何才能測(cè)試你的單例類(lèi)。

    測(cè)試單例模式

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

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

    例2兩次調(diào)用ClassicSingleton.getInstance(),并且把返回的引用存儲(chǔ)在成員變量中。方法testUnique()會(huì)檢查這些引用看它們是否相同。例3是這個(gè)測(cè)試案例的輸出:
    例3.是這個(gè)測(cè)試案例的輸出

     1Buildfile: build.xml 
     2  
     3init: 
     4     [echo] Build 20030414 (14-04-2003 03:08
     5  
     6compile: 
     7  
     8run-test-text: 
     9     [java] .INFO main: [b]getting singleton[/b] 
    10     [java] INFO main: [b]created singleton:[/b] Singleton@e86f41 
    11     [java] INFO main: got singleton: Singleton@e86f41 
    12     [java] INFO main: [b]getting singleton[/b] 
    13     [java] INFO main: got singleton: Singleton@e86f41 
    14     [java] INFO main: checking singletons for equality 
    15  
    16     [java] Time: 0.032 
    17  
    18     [java] OK (1 test)
    19


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

    多線程因素的考慮

    在例1中的ClassicSingleton.getInstance()方法由于下面的代碼而不是線程安全的:

    11if(instance == null
    22:    instance = new Singleton(); 
    33: }
     
    4
    如果一個(gè)線程在第二行的賦值語(yǔ)句發(fā)生之前切換,那么成員變量instance仍然是null,然后另一個(gè)線程可能接下來(lái)進(jìn)入到if塊中。在這種情況下,兩個(gè)不同的單例類(lèi)實(shí)例就被創(chuàng)建。不幸的是這種假定很少發(fā)生,這樣這種假定也很難在測(cè)試期間出現(xiàn)(譯注:在這可能是作者對(duì)很少出現(xiàn)這種情況而導(dǎo)致無(wú)法測(cè)試從而使人們放松警惕而感到嘆惜)。為了演示這個(gè)線程輪換,我得重新實(shí)現(xiàn)例1中的那個(gè)類(lèi)。例4就是修訂后的單例類(lèi):
    例4.人為安排的方式

     

     1import org.apache.log4j.Logger; 
     2  
     3public 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}
     
    35
    除了在這個(gè)清單中的單例類(lèi)強(qiáng)制使用了一個(gè)多線程錯(cuò)誤處理,例4類(lèi)似于例1中的單例類(lèi)。在getInstance()方法第一次被調(diào)用時(shí),調(diào)用這個(gè)方法的線程會(huì)休眠50毫秒以便另外的線程也有時(shí)間調(diào)用getInstance()并創(chuàng)建一個(gè)新的單例類(lèi)實(shí)例。當(dāng)休眠的線程覺(jué)醒時(shí),它也會(huì)創(chuàng)建一個(gè)新的單例類(lèi)實(shí)例,這樣我們就有兩個(gè)單例類(lèi)實(shí)例。盡管例4是人為如此的,但它卻模擬了第一個(gè)線程調(diào)用了getInstance()并在沒(méi)有完成時(shí)被切換的真實(shí)情形。
    例5測(cè)試了例4的單例類(lèi):
    例5.失敗的測(cè)試


     

     1import org.apache.log4j.Logger; 
     2import junit.framework.Assert; 
     3import junit.framework.TestCase; 
     4  
     5public 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}
     
    44

    例5的測(cè)試案例創(chuàng)建兩個(gè)線程,然后各自啟動(dòng),等待完成。這個(gè)案例保持了一個(gè)對(duì)單例類(lèi)的靜態(tài)引用,每個(gè)線程都會(huì)調(diào)用 Singleton.getInstance()。如果這個(gè)靜態(tài)成員變量沒(méi)有被設(shè)置,那么第一個(gè)線程就會(huì)將它設(shè)為通過(guò)調(diào)用getInstance()而得到的引用,然后這個(gè)靜態(tài)變量會(huì)與一個(gè)局部變量比較是否相等。
    在這個(gè)測(cè)試案例運(yùn)行時(shí)會(huì)發(fā)生一系列的事情:第一個(gè)線程調(diào)用getInstance(),進(jìn)入if塊,然后休眠;接著,第二個(gè)線程也調(diào)用 getInstance()并且創(chuàng)建了一個(gè)單例類(lèi)的實(shí)例。第二個(gè)線程會(huì)設(shè)置這個(gè)靜態(tài)成員變量為它所創(chuàng)建的引用。第二個(gè)線程檢查這個(gè)靜態(tài)成員變量與一個(gè)局部備份的相等性。然后測(cè)試通過(guò)。當(dāng)?shù)谝粋€(gè)線程覺(jué)醒時(shí),它也會(huì)創(chuàng)建一個(gè)單例類(lèi)的實(shí)例,并且它不會(huì)設(shè)置那個(gè)靜態(tài)成員變量(因?yàn)榈诙€(gè)線程已經(jīng)設(shè)置過(guò)了),所以那個(gè)靜態(tài)變量與那個(gè)局部變量脫離同步,相等性測(cè)試即告失敗。例6列出了例5的輸出:
    例6.例5的輸出

    Buildfile: build.xml 
    init: 
         [echo] Build 
    20030414 (14-04-2003 03:06
    compile: 
    run
    -test-text: 
    INFO Thread
    -1: sleeping 
    INFO Thread
    -2: created singleton: Singleton@7e5cbd 
    INFO Thread
    -1: created singleton: Singleton@704ebb 
    junit.framework.AssertionFailedError: expected: but was: 
       at junit.framework.Assert.fail(Assert.java:
    47
       at junit.framework.Assert.failNotEquals(Assert.java:
    282
       at junit.framework.Assert.assertEquals(Assert.java:
    64
       at junit.framework.Assert.assertEquals(Assert.java:
    149
       at junit.framework.Assert.assertEquals(Assert.java:
    155
       at SingletonTest$SingletonTestRunnable.run(Unknown Source) 
       at java.lang.Thread.run(Thread.java:
    554
         [java] . 
         [java] Time: 
    0.577 
      
         [java] OK (
    1 test) 
    到現(xiàn)在為止我們已經(jīng)知道例4不是線程安全的,那就讓我們看看如何修正它。


    同步

    要使例4的單例類(lèi)為線程安全的很容易----只要像下面一個(gè)同步化getInstance()方法:

     

    1public 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}
     
    9

    在同步化getInstance()方法后,我們就可以得到例5的測(cè)試案例返回的下面的結(jié)果:

     1Buildfile: build.xml 
     2  
     3init: 
     4     [echo] Build 20030414 (14-04-2003 03:15
     5  
     6compile: 
     7    [javac] Compiling 2 source files 
     8  
     9run-test-text: 
    10INFO Thread-1: sleeping 
    11INFO Thread-1: created singleton: Singleton@ef577d 
    12INFO Thread-2: created singleton: Singleton@ef577d 
    13     [java] . 
    14     [java] Time: 0.513 
    15  
    16     [java] OK (1 test) 
    17
    18
    這此,這個(gè)測(cè)試案例工作正常,并且多線程的煩惱也被解決;然而,機(jī)敏的讀者可能會(huì)認(rèn)識(shí)到getInstance()方法只需要在第一次被調(diào)用時(shí)同步。因?yàn)橥降男阅荛_(kāi)銷(xiāo)很昂貴(同步方法比非同步方法能降低到100次左右),或許我們可以引入一種性能改進(jìn)方法,它只同步單例類(lèi)的getInstance()方法中的賦值語(yǔ)句。

    一種性能改進(jìn)的方法

    尋找一種性能改進(jìn)方法時(shí),你可能會(huì)選擇像下面這樣重寫(xiě)getInstance()方法:

     

    1public static Singleton getInstance() 
    2   if(singleton == null
    3      synchronized(Singleton.class{  
    4         singleton = new Singleton(); 
    5      }
     
    6   }
     
    7   return singleton; 
    8}
     
    9
    這個(gè)代碼片段只同步了關(guān)鍵的代碼,而不是同步整個(gè)方法。然而這段代碼卻不是線程安全的。考慮一下下面的假定:線程1進(jìn)入同步塊,并且在它給 singleton成員變量賦值之前線程1被切換。接著另一個(gè)線程進(jìn)入if塊。第二個(gè)線程將等待直到第一個(gè)線程完成,并且仍然會(huì)得到兩個(gè)不同的單例類(lèi)實(shí)例。有修復(fù)這個(gè)問(wèn)題的方法嗎?請(qǐng)讀下去。

    雙重加鎖檢查

    初看上去,雙重加鎖檢查似乎是一種使懶漢式實(shí)例化為線程安全的技術(shù)。下面的代碼片段展示了這種技術(shù):

     

     1public 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}
     
    11

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

    一個(gè)改進(jìn)的線程安全的單例模式實(shí)現(xiàn)

    例7列出了一個(gè)簡(jiǎn)單、快速而又是線程安全的單例模式實(shí)現(xiàn):
    例7.一個(gè)簡(jiǎn)單的單例類(lèi)

    1public class Singleton 
    2   public final static Singleton INSTANCE = new Singleton(); 
    3   private Singleton() 
    4         // Exists only to defeat instantiation. 
    5      }
     
    6}
     
    7
    8
    這段代碼是線程安全的是因?yàn)殪o態(tài)成員變量一定會(huì)在類(lèi)被第一次訪問(wèn)時(shí)被創(chuàng)建。你得到了一個(gè)自動(dòng)使用了懶漢式實(shí)例化的線程安全的實(shí)現(xiàn);你應(yīng)該這樣使用它:

     

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

    你可以安全的使用例7的單例模式實(shí)現(xiàn)或者是例1的帶一個(gè)同步的getInstance()方法的實(shí)現(xiàn).然而,我們必須要研究另一個(gè)問(wèn)題:你必須在編譯期指定這個(gè)單例類(lèi),這樣就不是很靈活.一個(gè)單例類(lèi)的注冊(cè)表會(huì)讓我們?cè)谶\(yùn)行期指定一個(gè)單例類(lèi).

    使用注冊(cè)表
    使用一個(gè)單例類(lèi)注冊(cè)表可以:

    在運(yùn)行期指定單例類(lèi)

    防止產(chǎn)生多個(gè)單例類(lèi)子類(lèi)的實(shí)例
    在例8的單例類(lèi)中,保持了一個(gè)通過(guò)類(lèi)名進(jìn)行注冊(cè)的單例類(lèi)注冊(cè)表:
    例8 帶注冊(cè)表的單例類(lèi)


     

     1import java.util.HashMap; 
     2import org.apache.log4j.Logger; 
     3  
     4public class Singleton 
     5   private static HashMap map = new HashMap(); 
     6   private static Logger logger = Logger.getRootLogger(); 
     7  
     8   protected Singleton() 
     9      // Exists only to thwart instantiation 
    10   }
     
    11   public static synchronized Singleton getInstance(String classname) 
    12      if(classname == nullthrow new IllegalArgumentException("Illegal classname"); 
    13         Singleton singleton = (Singleton)map.get(classname); 
    14  
    15      if(singleton != null
    16         logger.info("got singleton from map: " + singleton); 
    17         return singleton; 
    18      }
     
    19      if(classname.equals("SingeltonSubclass_One")) 
    20            singleton = new SingletonSubclass_One();          
    21         else if(classname.equals("SingeltonSubclass_Two")) 
    22            singleton = new SingletonSubclass_Two(); 
    23  
    24      map.put(classname, singleton); 
    25      logger.info("created singleton: " + singleton); 
    26      return singleton; 
    27   }
     
    28   // Assume functionality follows that's attractive to inherit 
    29}
     
    30
    這段代碼的基類(lèi)首先創(chuàng)建出子類(lèi)的實(shí)例,然后把它們存儲(chǔ)在一個(gè)Map中。但是基類(lèi)卻得付出很高的代價(jià)因?yàn)槟惚仨殲槊恳粋€(gè)子類(lèi)替換它的getInstance()方法。幸運(yùn)的是我們可以使用反射處理這個(gè)問(wèn)題。

    使用反射

    在例9的帶注冊(cè)表的單例類(lèi)中,使用反射來(lái)實(shí)例化一個(gè)特殊的類(lèi)的對(duì)象。與例8相對(duì)的是通過(guò)這種實(shí)現(xiàn),Singleton.getInstance()方法不需要在每個(gè)被實(shí)現(xiàn)的子類(lèi)中重寫(xiě)了。
    例9 使用反射實(shí)例化單例類(lèi)

     

     1import java.util.HashMap; 
     2import org.apache.log4j.Logger; 
     3  
     4public class Singleton 
     5   private static HashMap map = new HashMap(); 
     6   private static Logger logger = Logger.getRootLogger(); 
     7  
     8   protected Singleton() 
     9      // Exists only to thwart instantiation 
    10   }
     
    11   public static synchronized Singleton getInstance(String classname) 
    12      Singleton singleton = (Singleton)map.get(classname); 
    13  
    14      if(singleton != null
    15         logger.info("got singleton from map: " + singleton); 
    16         return singleton; 
    17      }
     
    18      try 
    19         singleton = (Singleton)Class.forName(classname).newInstance(); 
    20      }
     
    21      catch(ClassNotFoundException cnf) 
    22         logger.fatal("Couldn't find class " + classname);     
    23      }
     
    24      catch(InstantiationException ie) 
    25         logger.fatal("Couldn't instantiate an object of type " + classname);     
    26      }
     
    27      catch(IllegalAccessException ia) 
    28         logger.fatal("Couldn't access class " + classname);     
    29      }
     
    30      map.put(classname, singleton); 
    31      logger.info("created singleton: " + singleton); 
    32  
    33      return singleton; 
    34   }
     
    35}
     
    36
    關(guān)于單例類(lèi)的注冊(cè)表應(yīng)該說(shuō)明的是:它們應(yīng)該被封裝在它們自己的類(lèi)中以便最大限度的進(jìn)行復(fù)用。


    封裝注冊(cè)表

    例10列出了一個(gè)單例注冊(cè)表類(lèi)。
    例10 一個(gè)SingletonRegistry類(lèi)

     

     1import java.util.HashMap; 
     2import org.apache.log4j.Logger; 
     3  
     4public class SingletonRegistry 
     5   public static SingletonRegistry REGISTRY = new SingletonRegistry(); 
     6  
     7   private static HashMap map = new HashMap(); 
     8   private static Logger logger = Logger.getRootLogger(); 
     9  
    10   protected SingletonRegistry() 
    11      // Exists to defeat instantiation 
    12   }
     
    13   public static synchronized Object getInstance(String classname) 
    14      Object singleton = map.get(classname); 
    15  
    16      if(singleton != null
    17         return singleton; 
    18      }
     
    19      try 
    20         singleton = Class.forName(classname).newInstance(); 
    21         logger.info("created singleton: " + singleton); 
    22      }
     
    23      catch(ClassNotFoundException cnf) 
    24         logger.fatal("Couldn't find class " + classname);     
    25      }
     
    26      catch(InstantiationException ie) 
    27         logger.fatal("Couldn't instantiate an object of type " +  
    28                       classname);     
    29      }
     
    30      catch(IllegalAccessException ia) 
    31         logger.fatal("Couldn't access class " + classname);     
    32      }
     
    33      map.put(classname, singleton); 
    34      return singleton; 
    35   }
     
    36}
     
    37
    38

    注意我是把SingletonRegistry類(lèi)作為一個(gè)單例模式實(shí)現(xiàn)的。我也通用化了這個(gè)注冊(cè)表以便它能存儲(chǔ)和取回任何類(lèi)型的對(duì)象。例11顯示了的Singleton類(lèi)使用了這個(gè)注冊(cè)表。
    例11 使用了一個(gè)封裝的注冊(cè)表的Singleton類(lèi)

     1import java.util.HashMap; 
     2import org.apache.log4j.Logger; 
     3  
     4public class Singleton 
     5  
     6   protected Singleton() 
     7      // Exists only to thwart instantiation. 
     8   }
     
     9   public static Singleton getInstance() 
    10      return (Singleton)SingletonRegistry.REGISTRY.getInstance(classname); 
    11   }
     
    12}
     
    13

    上面的Singleton類(lèi)使用那個(gè)注冊(cè)表的唯一實(shí)例通過(guò)類(lèi)名取得單例對(duì)象。
    現(xiàn)在我們已經(jīng)知道如何實(shí)現(xiàn)線程安全的單例類(lèi)和如何使用一個(gè)注冊(cè)表去在運(yùn)行期指定單例類(lèi)名,接著讓我們考查一下如何安排類(lèi)載入器和處理序列化。

    Classloaders

    在許多情況下,使用多個(gè)類(lèi)載入器是很普通的--包括servlet容器--所以不管你在實(shí)現(xiàn)你的單例類(lèi)時(shí)是多么小心你都最終可以得到多個(gè)單例類(lèi)的實(shí)例。如果你想要確保你的單例類(lèi)只被同一個(gè)的類(lèi)載入器裝入,那你就必須自己指定這個(gè)類(lèi)載入器;例如:

     1private static Class getClass(String classname)  
     2                                         throws ClassNotFoundException 
     3      ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 
     4  
     5      if(classLoader == null
     6         classLoader = Singleton.class.getClassLoader(); 
     7  
     8      return (classLoader.loadClass(classname)); 
     9   }
     
    10
    11
    這個(gè)方法會(huì)嘗試把當(dāng)前的線程與那個(gè)類(lèi)載入器相關(guān)聯(lián);如果classloader為null,這個(gè)方法會(huì)使用與裝入單例類(lèi)基類(lèi)的那個(gè)類(lèi)載入器。這個(gè)方法可以用Class.forName()代替。

    序列化

    如果你序列化一個(gè)單例類(lèi),然后兩次重構(gòu)它,那么你就會(huì)得到那個(gè)單例類(lèi)的兩個(gè)實(shí)例,除非你實(shí)現(xiàn)readResolve()方法,像下面這樣:
    例12 一個(gè)可序列化的單例類(lèi)

     

     1import org.apache.log4j.Logger; 
     2  
     3public class Singleton implements java.io.Serializable 
     4   public static Singleton INSTANCE = new Singleton(); 
     5  
     6   protected Singleton() 
     7      // Exists only to thwart instantiation. 
     8   }
     
     9   private Object readResolve() 
    10            return INSTANCE; 
    11      }

    12   }
     
    13
    上面的單例類(lèi)實(shí)現(xiàn)從readResolve()方法中返回一個(gè)唯一的實(shí)例;這樣無(wú)論Singleton類(lèi)何時(shí)被重構(gòu),它都只會(huì)返回那個(gè)相同的單例類(lèi)實(shí)例。
    例13測(cè)試了例12的單例類(lèi):
    例13 測(cè)試一個(gè)可序列化的單例類(lèi)

     

     1import java.io.*
     2import org.apache.log4j.Logger; 
     3import junit.framework.Assert; 
     4import junit.framework.TestCase; 
     5  
     6public class SingletonTest extends TestCase 
     7   private Singleton sone = null, stwo = null
     8   private static Logger logger = Logger.getRootLogger(); 
     9  
    10   public SingletonTest(String name) 
    11      super(name); 
    12   }
     
    13   public void setUp() 
    14      sone = Singleton.INSTANCE; 
    15      stwo = Singleton.INSTANCE; 
    16   }
     
    17   public void testSerialize() 
    18      logger.info("testing singleton serialization"); 
    19[b]      writeSingleton(); 
    20      Singleton s1 = readSingleton(); 
    21      Singleton s2 = readSingleton(); 
    22      Assert.assertEquals(true, s1 == s2);[/b]   }
     
    23   private void writeSingleton() 
    24      try 
    25         FileOutputStream fos = new FileOutputStream("serializedSingleton"); 
    26         ObjectOutputStream oos = new ObjectOutputStream(fos); 
    27         Singleton s = Singleton.INSTANCE; 
    28  
    29         oos.writeObject(Singleton.INSTANCE); 
    30         oos.flush(); 
    31      }
     
    32      catch(NotSerializableException se) 
    33         logger.fatal("Not Serializable Exception: " + se.getMessage()); 
    34      }
     
    35      catch(IOException iox) 
    36         logger.fatal("IO Exception: " + iox.getMessage()); 
    37      }
     
    38   }
     
    39   private Singleton readSingleton() 
    40      Singleton s = null
    41  
    42      try 
    43         FileInputStream fis = new FileInputStream("serializedSingleton"); 
    44         ObjectInputStream ois = new ObjectInputStream(fis); 
    45         s = (Singleton)ois.readObject(); 
    46      }
     
    47      catch(ClassNotFoundException cnf) 
    48         logger.fatal("Class Not Found Exception: " + cnf.getMessage()); 
    49      }
     
    50      catch(NotSerializableException se) 
    51         logger.fatal("Not Serializable Exception: " + se.getMessage()); 
    52      }
     
    53      catch(IOException iox) 
    54         logger.fatal("IO Exception: " + iox.getMessage()); 
    55      }
     
    56      return s; 
    57   }
     
    58   public void testUnique() 
    59      logger.info("testing singleton uniqueness"); 
    60      Singleton another = new Singleton(); 
    61  
    62      logger.info("checking singletons for equality"); 
    63      Assert.assertEquals(true, sone == stwo); 
    64   }
     
    65}
     
    66

    前面這個(gè)測(cè)試案例序列化例12中的單例類(lèi),并且兩次重構(gòu)它。然后這個(gè)測(cè)試案例檢查看是否被重構(gòu)的單例類(lèi)實(shí)例是同一個(gè)對(duì)象。下面是測(cè)試案例的輸出:

    Buildfile: build.xml 
      
    init: 
         [echo] Build 
    20030422 (22-04-2003 11:32
      
    compile: 
      
    run
    -test-text: 
         [java] .INFO main: testing singleton serialization 
         [java] .INFO main: testing singleton uniqueness 
         [java] INFO main: checking singletons 
    for equality 
      
         [java] Time: 
    0.1 
      
         [java] OK (
    2 tests) 

    單例模式結(jié)束語(yǔ)

    單例模式簡(jiǎn)單卻容易讓人迷惑,特別是對(duì)于Java的開(kāi)發(fā)者來(lái)說(shuō)。在這篇文章中,作者演示了Java開(kāi)發(fā)者在顧及多線程、類(lèi)載入器和序列化情況如何實(shí)現(xiàn)單例模式。作者也展示了你怎樣才能實(shí)現(xiàn)一個(gè)單例類(lèi)的注冊(cè)表,以便能夠在運(yùn)行期指定單例類(lèi)。













     

    posted on 2011-02-17 11:16 CONAN 閱讀(247) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): JAVA模式
    主站蜘蛛池模板: 亚洲AV无码一区二区三区在线观看| 麻豆一区二区三区蜜桃免费| 亚洲麻豆精品果冻传媒| 亚洲无人区午夜福利码高清完整版| 波多野结衣中文一区二区免费| 永久在线毛片免费观看| 无码国模国产在线观看免费| 四虎免费大片aⅴ入口| 日韩毛片无码永久免费看| 国产伦一区二区三区免费| 免费a级毛片网站| 亚洲AⅤ永久无码精品AA| 亚洲 无码 在线 专区| 亚洲精品无码久久毛片| 免费a级毛片无码a∨性按摩| 亚洲福利精品一区二区三区| 亚洲美女高清一区二区三区 | 免费国产a理论片| 免费精品国产自产拍在线观看| 日韩a毛片免费观看| 一级美国片免费看| 三级网站免费观看| 久草免费福利资源站| 精品福利一区二区三区免费视频| 67pao强力打造高清免费| 国产精品免费观看久久| 日韩免费毛片视频| 亚洲麻豆精品国偷自产在线91| 亚洲线精品一区二区三区影音先锋| 亚洲大尺度无码专区尤物| 中文字幕亚洲综合精品一区| 亚洲宅男精品一区在线观看| 蜜芽亚洲av无码一区二区三区| 男女啪啪免费体验区| a级午夜毛片免费一区二区| 18禁成人网站免费观看| 毛片免费全部播放一级| 亚洲国产av无码精品| 日本亚洲欧洲免费天堂午夜看片女人员 | 日韩一级片免费观看| h片在线播放免费高清 |