一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

單例模式具體實(shí)現(xiàn)

 木有銀 2010-04-23
概要
單例模式是最簡(jiǎn)單的設(shè)計(jì)模式之一,但是對(duì)于Java的開發(fā)者來(lái)說(shuō),它卻有很多缺陷。在本月的專欄中,David Geary探討了單例模式以及在面對(duì)多線程(multithreading)、類裝載器(classloaders)和序列化(serialization)時(shí)如何處理這些缺陷。

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


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

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

單例模式

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

單例模式的類圖

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

Java代碼 復(fù)制代碼
  1. public 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. }   


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


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

測(cè)試單例模式

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

例2是一個(gè)用JUnit測(cè)試?yán)?的單例模式的案例:
例2.一個(gè)單例模式的案例

Java代碼 復(fù)制代碼
  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 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. }   


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

Java代碼 復(fù)制代碼
  1. Buildfile: build.xml    
  2.      
  3. init:    
  4.      [echo] Build 20030414 (14-04-2003 03:08)    
  5.      
  6. compile:    
  7.      
  8. run-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)  


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


多線程因素的考慮

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


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

Java代碼 復(fù)制代碼
  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. }   


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

Java代碼 復(fù)制代碼
  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的測(cè)試案例創(chuàng)建兩個(gè)線程,然后各自啟動(dòng),等待完成。這個(gè)案例保持了一個(gè)對(duì)單例類的靜態(tài)引用,每個(gè)線程都會(huì)調(diào)用Singleton.getInstance()。如果這個(gè)靜態(tà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è)單例類的實(shí)例。第二個(gè)線程會(huì)設(shè)置這個(gè)靜態(tài)成員變量為它所創(chuàng)建的引用。第二個(gè)線程檢查這個(gè)靜態(tài)成員變量與一個(gè)局部備份的相等性。然后測(cè)試通過(guò)。當(dāng)?shù)谝粋€(gè)線程覺醒時(shí),它也會(huì)創(chuàng)建一個(gè)單例類的實(shí)例,并且它不會(huì)設(shè)置那個(gè)靜態(tài)成員變量(因?yàn)榈诙€(gè)線程已經(jīng)設(shè)置過(guò)了),所以那個(gè)靜態(tài)變量與那個(gè)局部變量脫離同步,相等性測(cè)試即告失敗。例6列出了例5的輸出:
例6.例5的輸出

Java代碼 復(fù)制代碼
  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)   


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


同步

要使例4的單例類為線程安全的很容易----只要像下面一個(gè)同步化getInstance()方法:
Java代碼 復(fù)制代碼
  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的測(cè)試案例返回的下面的結(jié)果:
Java代碼 復(fù)制代碼
  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)   


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

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

尋找一種性能改進(jìn)方法時(shí),你可能會(huì)選擇像下面這樣重寫getInstance()方法:
Java代碼 復(fù)制代碼
  1. public static Singleton getInstance() {    
  2.    if(singleton == null) {    
  3.       synchronized(Singleton.class) {     
  4.          singleton = new Singleton();    
  5.       }    
  6.    }    
  7.    return singleton;    
  8. }   


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

雙重加鎖檢查

初看上去,雙重加鎖檢查似乎是一種使懶漢式實(shí)例化為線程安全的技術(shù)。下面的代碼片段展示了這種技術(shù):
Java代碼 復(fù)制代碼
  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. }   


如果兩個(gè)線程同時(shí)訪問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è)單例類實(shí)例也就不會(huì)被創(chuàng)建。似乎就是如此。
不幸的是,雙重加鎖檢查不會(huì)保證正常工作,因?yàn)榫幾g器會(huì)在Singleton的構(gòu)造方法被調(diào)用之前隨意給singleton賦一個(gè)值。如果在singleton引用被賦值之后而被初始化之前線程1被切換,線程2就會(huì)被返回一個(gè)對(duì)未初始化的單例類實(shí)例的引用。

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

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


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


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

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

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

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

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

Java代碼 復(fù)制代碼
  1. import java.util.HashMap;    
  2. import org.apache.log4j.Logger;    
  3.      
  4. public 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. }   


這段代碼的基類首先創(chuàng)建出子類的實(shí)例,然后把它們存儲(chǔ)在一個(gè)Map中。但是基類卻得付出很高的代價(jià)因?yàn)槟惚仨殲槊恳粋€(gè)子類替換它的getInstance()方法。幸運(yùn)的是我們可以使用反射處理這個(gè)問題。

使用反射

在例9的帶注冊(cè)表的單例類中,使用反射來(lái)實(shí)例化一個(gè)特殊的類的對(duì)象。與例8相對(duì)的是通過(guò)這種實(shí)現(xiàn),Singleton.getInstance()方法不需要在每個(gè)被實(shí)現(xiàn)的子類中重寫了。
例9 使用反射實(shí)例化單例類
Java代碼 復(fù)制代碼
  1. import java.util.HashMap;    
  2. import org.apache.log4j.Logger;    
  3.      
  4. public 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. }   


關(guān)于單例類的注冊(cè)表應(yīng)該說(shuō)明的是:它們應(yīng)該被封裝在它們自己的類中以便最大限度的進(jìn)行復(fù)用。


封裝注冊(cè)表

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

Java代碼 復(fù)制代碼
  1. import java.util.HashMap;    
  2. import org.apache.log4j.Logger;    
  3.      
  4. public 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. }   


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

Java代碼 復(fù)制代碼
  1. import java.util.HashMap;    
  2. import org.apache.log4j.Logger;    
  3.      
  4. public 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. }   


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

Classloaders

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

Java代碼 復(fù)制代碼
  1. private 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. }   


這個(gè)方法會(huì)嘗試把當(dāng)前的線程與那個(gè)類載入器相關(guān)聯(lián);如果classloader為null,這個(gè)方法會(huì)使用與裝入單例類基類的那個(gè)類載入器。這個(gè)方法可以用Class.forName()代替。

序列化

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

Java代碼 復(fù)制代碼
  1. import org.apache.log4j.Logger;    
  2.      
  3. public 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.    }   


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

Java代碼 復(fù)制代碼
  1. import java.io.*;    
  2. import org.apache.log4j.Logger;    
  3. import junit.framework.Assert;    
  4. import junit.framework.TestCase;    
  5.      
  6. public 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. }   


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

Java代碼 復(fù)制代碼
  1. Buildfile: build.xml    
  2.      
  3. init:    
  4.      [echo] Build 20030422 (22-04-2003 11:32)    
  5.      
  6. compile:    
  7.      
  8. run-test-text:    
  9.      [java] .INFO main: testing singleton serialization...    
  10.      [java] .INFO main: testing singleton uniqueness...    
  11.      [java] INFO main: checking singletons for equality    
  12.      
  13.      [java] Time: 0.1    
  14.      
  15.      [java] OK (2 tests)   


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

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

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    日韩精品一区二区三区含羞含羞草 | 日本加勒比不卡二三四区| 精品伊人久久大香线蕉综合| 91欧美亚洲视频在线| 国产午夜精品美女露脸视频| 欧美日本精品视频在线观看| 国产乱久久亚洲国产精品| 国产中文字幕久久黄色片| 在线亚洲成人中文字幕高清 | 国产欧美日韩一级小黄片| 好吊日在线视频免费观看| 国产传媒中文字幕东京热| 亚洲一区二区精品国产av | 深夜视频在线观看免费你懂| 丰满人妻熟妇乱又伦精另类视频| 久久这里只精品免费福利| 亚洲综合一区二区三区在线| 亚洲国产另类久久精品| 欧美六区视频在线观看| 欧美一区二区三区喷汁尤物| 91欧美一区二区三区| 午夜亚洲精品理论片在线观看| 丁香七月啪啪激情综合| 国产成人高清精品尤物| 一区二区日韩欧美精品| 一区二区日韩欧美精品| 亚洲一区二区三在线播放| 亚洲成人黄色一级大片| 日韩一区二区三区在线日| 日韩日韩日韩日韩在线| 自拍偷拍一区二区三区| 国产精品自拍杆香蕉视频| 亚洲午夜福利不卡片在线 | 午夜激情视频一区二区| 91免费精品国自产拍偷拍| 视频一区二区三区自拍偷| 国产精品免费不卡视频| 五月婷婷综合缴情六月| 午夜精品久久久99热连载| 欧美日韩国产另类一区二区 | 久热这里只有精品九九|