`
litton
  • 浏览: 11301 次
  • 性别: Icon_minigender_1
  • 来自: 北京
最近访客 更多访客>>
社区版块
存档分类
最新评论

转:单例模式

    博客分类:
  • java
阅读更多

概要
单例模式是最简单的设计模式之一,但是对于Java的开发者来说,它却有很多缺陷。在本月的专栏中,David Geary探讨了单例模式以及在面对多线程(multithreading)、类装载器(classloaders)和序列化(serialization)时如何处理这些缺陷。

单例模式适合于一个类只有一个实例的情况,比如窗口管理器,打印缓冲池和文件系统,它们都是原型的例子。典型的情况是,那些对象的类型被遍及一个软件系统的不同对象访问,因此需要一个全局的访问指针,这便是众所周知的单例模式的应用。当然这只有在你确信你不再需要任何多于一个的实例的情况下。
单例模式的用意在于前一段中所关心的。通过单例模式你可以:


确保一个类只有一个实例被建立
提供了一个对对象的全局访问指针
在不影响单例类的客户端的情况下允许将来有多个实例

尽管单例设计模式如在下面的图中的所显示的一样是最简单的设计模式,但对于粗心的Java开发者来说却呈现出许多缺陷。这篇文章讨论了单例模式并揭示了那些缺陷。
注意:你可以从Resources下载这篇文章的源代码。

单例模式

在《设计模式》一书中,作者这样来叙述单例模式的:确保一个类只有一个实例并提供一个对它的全局访问指针。
下图说明了单例模式的类图。
(图1)

单例模式的类图

正如你在上图中所看到的,这不是单例模式的完整部分。此图中单例类保持了一个对唯一的单例实例的静态引用,并且会从静态getInstance()方法中返回对那个实例的引用。
例1显示了一个经典的单例模式的实现。
例1.经典的单例模式

Java代码 复制代码
  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. }   
public class ClassicSingleton { 
   private static ClassicSingleton instance = null; 
  
   protected ClassicSingleton() { 
      // Exists only to defeat instantiation. 
   } 
   public static ClassicSingleton getInstance() { 
      if(instance == null) { 
         instance = new ClassicSingleton(); 
      } 
      return instance; 
   } 
} 



在例1中的单例模式的实现很容易理解。ClassicSingleton类保持了一个对单独的单例实例的静态引用,并且从静态方法getInstance()中返回那个引用。
关于ClassicSingleton类,有几个让我们感兴趣的地方。首先,ClassicSingleton使用了一个众所周知的懒汉式实例化去创建那个单例类的引用;结果,这个单例类的实例直到getInstance()方法被第一次调用时才被创建。这种技巧可以确保单例类的实例只有在需要时才被建立出来。其次,注意ClassicSingleton实现了一个protected的构造方法,这样客户端不能直接实例化一个ClassicSingleton类的实例。然而,你会惊奇的发现下面的代码完全合法:

Java代码 复制代码
  1. public class SingletonInstantiator {     
  2.   public SingletonInstantiator() {     
  3.    ClassicSingleton instance = ClassicSingleton.getInstance();    
  4. ClassicSingleton anotherInstance =    
  5. new ClassicSingleton();    
  6.        ...     
  7.   }     
  8. }   
public class SingletonInstantiator {  
  public SingletonInstantiator() {  
   ClassicSingleton instance = ClassicSingleton.getInstance(); 
ClassicSingleton anotherInstance = 
new ClassicSingleton(); 
       ...  
  }  
} 



前面这个代码片段为何能在没有继承ClassicSingleton并且ClassicSingleton类的构造方法是protected的情况下创建其实例?答案是protected的构造方法可以被其子类以及在同一个包中的其它类调用。因为ClassicSingleton和SingletonInstantiator位于相同的包(缺省的包),所以SingletonInstantiator方法能创建ClasicSingleton的实例。
这种情况下有两种解决方案:一是你可以使ClassicSingleton的构造方法变化私有的(private)这样只有ClassicSingleton的方法能调用它;然而这也意味着ClassicSingleton不能有子类。有时这是一种很合意的解决方法,如果确实如此,那声明你的单例类为final是一个好主意,这样意图明确,并且让编译器去使用一些性能优化选项。另一种解决方法是把你的单例类放到一个外在的包中,以便在其它包中的类(包括缺省的包)无法实例化一个单例类。
关于ClassicSingleton的第三点感兴趣的地方是,如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。
第四点,如果ClasicSingleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。
最后也许是最重要的一点,就是例1中的ClassicSingleton类不是线程安全的。如果两个线程,我们称它们为线程1和线程2,在同一时间调用ClassicSingleton.getInstance()方法,如果线程1先进入if块,然后线程2进行控制,那么就会有ClassicSingleton的两个的实例被创建。

正如你从前面的讨论中所看到的,尽管单例模式是最简单的设计模式之一,在Java中实现它也是决非想象的那么简单。这篇文章接下来会揭示Java规范对单例模式进行的考虑,但是首先让我们近水楼台的看看你如何才能测试你的单例类。

测试单例模式

接下来,我使用与log4j相对应的JUnit来测试单例类,它会贯穿在这篇文章余下的部分。如果你对JUnit或log4j不很熟悉,请参考相关资源。

例2是一个用JUnit测试例1的单例模式的案例:
例2.一个单例模式的案例

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 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. }   
import org.apache.log4j.Logger; 
import junit.framework.Assert; 
import junit.framework.TestCase; 
  
public class SingletonTest extends TestCase { 
   private ClassicSingleton sone = null, stwo = null; 
   private static Logger logger = Logger.getRootLogger(); 
  
   public SingletonTest(String name) { 
      super(name); 
   } 
   public void setUp() { 
      logger.info("getting singleton..."); 
      sone = ClassicSingleton.getInstance(); 
      logger.info("...got singleton: " + sone); 
  
      logger.info("getting singleton..."); 
      stwo = ClassicSingleton.getInstance(); 
      logger.info("...got singleton: " + stwo); 
   } 
   public void testUnique() { 
      logger.info("checking singletons for equality"); 
      Assert.assertEquals(true, sone == stwo); 
   } 
} 



例2两次调用ClassicSingleton.getInstance(),并且把返回的引用存储在成员变量中。方法testUnique()会检查这些引用看它们是否相同。例3是这个测试案例的输出:
例3.是这个测试案例的输出

Java代码 复制代码
  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)  
Buildfile: build.xml 
  
init: 
     [echo] Build 20030414 (14-04-2003 03:08) 
  
compile: 
  
run-test-text: 
     [java] .INFO main: [b]getting singleton...[/b] 
     [java] INFO main: [b]created singleton:[/b] Singleton@e86f41 
     [java] INFO main: ...got singleton: Singleton@e86f41 
     [java] INFO main: [b]getting singleton...[/b] 
     [java] INFO main: ...got singleton: Singleton@e86f41 
     [java] INFO main: checking singletons for equality 
  
     [java] Time: 0.032 
  
     [java] OK (1 test)



正如前面的清单所示,例2的简单测试顺利通过----通过ClassicSingleton.getInstance()获得的两个单例类的引用确实相同;然而,你要知道这些引用是在单线程中得到的。下面的部分着重于用多线程测试单例类。


多线程因素的考虑

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

Java代码 复制代码
  1. 1if(instance == null) {    
  2. 2:    instance = new Singleton();    
  3. 3: }   
1: if(instance == null) { 
2:    instance = new Singleton(); 
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. }   
import org.apache.log4j.Logger; 
  
public class Singleton { 
  private static Singleton singleton = null; 
  private static Logger logger = Logger.getRootLogger(); 
  private static boolean firstThread = true; 
  
  protected Singleton() { 
    // Exists only to defeat instantiation. 
  } 
  public static Singleton getInstance() { 
     if(singleton == null) { 
        simulateRandomActivity(); 
        singleton = new Singleton(); 
     } 
     logger.info("created singleton: " + singleton); 
     return singleton; 
  } 
  private static void simulateRandomActivity() { 
     try { 
        if(firstThread) { 
           firstThread = false; 
           logger.info("sleeping..."); 
  
           // This nap should give the second thread enough time 
           // to get by the first thread. 
             Thread.currentThread().sleep(50); 
       } 
     } 
     catch(InterruptedException ex) { 
        logger.warn("Sleep interrupted"); 
     } 
  } 
} 



除了在这个清单中的单例类强制使用了一个多线程错误处理,例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. }   
import org.apache.log4j.Logger; 
import junit.framework.Assert; 
import junit.framework.TestCase; 
  
public class SingletonTest extends TestCase { 
   private static Logger logger = Logger.getRootLogger(); 
   private static Singleton singleton = null; 
  
   public SingletonTest(String name) { 
      super(name); 
   } 
   public void setUp() { 
      singleton = null; 
   } 
   public void testUnique() throws InterruptedException { 
      // Both threads call Singleton.getInstance(). 
      Thread threadOne = new Thread(new SingletonTestRunnable()), 
             threadTwo = new Thread(new SingletonTestRunnable()); 
  
      threadOne.start(); 
      threadTwo.start(); 
  
      threadOne.join(); 
      threadTwo.join(); 
   } 
   private static class SingletonTestRunnable implements Runnable { 
      public void run() { 
         // Get a reference to the singleton. 
         Singleton s = Singleton.getInstance(); 
  
         // Protect singleton member variable from 
         // multithreaded access. 
         synchronized(SingletonTest.class) { 
            if(singleton == null) // If local reference is null... 
               singleton = s;     // ...set it to the singleton 
         } 
         // Local reference must be equal to the one and 
         // only instance of Singleton; otherwise, we have two 
                  // Singleton instances. 
         Assert.assertEquals(true, s == singleton); 
      } 
   } 
} 



例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)   
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) 



到现在为止我们已经知道例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. }   
public synchronized static Singleton getInstance() { 
   if(singleton == null) { 
      simulateRandomActivity(); 
      singleton = new Singleton(); 
   } 
   logger.info("created singleton: " + singleton); 
   return singleton; 
} 


在同步化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)   
Buildfile: build.xml 
  
init: 
     [echo] Build 20030414 (14-04-2003 03:15) 
  
compile: 
    [javac] Compiling 2 source files 
  
run-test-text: 
INFO Thread-1: sleeping... 
INFO Thread-1: created singleton: Singleton@ef577d 
INFO Thread-2: created singleton: Singleton@ef577d 
     [java] . 
     [java] Time: 0.513 
  
     [java] OK (1 test) 



这此,这个测试案例工作正常,并且多线程的烦恼也被解决;然而,机敏的读者可能会认识到getInstance()方法只需要在第一次被调用时同步。因为同步的性能开销很昂贵(同步方法比非同步方法能降低到100次左右),或许我们可以引入一种性能改进方法,它只同步单例类的getInstance()方法中的赋值语句。

一种性能改进的方法

寻找一种性能改进方法时,你可能会选择像下面这样重写getInstance()方法:

Java代码 复制代码
  1. public static Singleton getInstance() {    
  2.    if(singleton == null) {    
  3.       synchronized(Singleton.class) {     
  4.          singleton = new Singleton();    
  5.       }    
  6.    }    
  7.    return singleton;    
  8. }   
public static Singleton getInstance() { 
   if(singleton == null) { 
      synchronized(Singleton.class) {  
         singleton = new Singleton(); 
      } 
   } 
   return singleton; 
} 



这个代码片段只同步了关键的代码,而不是同步整个方法。然而这段代码却不是线程安全的。考虑一下下面的假定:线程1进入同步块,并且在它给singleton成员变量赋值之前线程1被切换。接着另一个线程进入if块。第二个线程将等待直到第一个线程完成,并且仍然会得到两个不同的单例类实例。有修复这个问题的方法吗?请读下去。

双重加锁检查

初看上去,双重加锁检查似乎是一种使懒汉式实例化为线程安全的技术。下面的代码片段展示了这种技术:

Java代码 复制代码
  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. }   
public static Singleton getInstance() { 
  if(singleton == null) { 
     synchronized(Singleton.class) { 
       if(singleton == null) { 
         singleton = new Singleton(); 
       } 
    } 
  } 
  return singleton; 
} 



如果两个线程同时访问getInstance()方法会发生什么?想像一下线程1进行同步块马上又被切换。接着,第二个线程进入if 块。当线程1退出同步块时,线程2会重新检查看是否singleton实例仍然为null。因为线程1设置了singleton成员变量,所以线程2的第二次检查会失败,第二个单例类实例也就不会被创建。似乎就是如此。
不幸的是,双重加锁检查不会保证正常工作,因为编译器会在Singleton的构造方法被调用之前随意给singleton赋一个值。如果在singleton引用被赋值之后而被初始化之前线程1被切换,线程2就会被返回一个对未初始化的单例类实例的引用。

一个改进的线程安全的单例模式实现

例7列出了一个简单、快速而又是线程安全的单例模式实现:
例7.一个简单的单例类

Java代码 复制代码
  1. public class Singleton {    
  2.    public final static Singleton INSTANCE = new Singleton();    
  3.    private Singleton() {    
  4.          // Exists only to defeat instantiation.    
  5.       }    
  6. }   
public class Singleton { 
   public final static Singleton INSTANCE = new Singleton(); 
   private Singleton() { 
         // Exists only to defeat instantiation. 
      } 
} 



这段代码是线程安全的是因为静态成员变量一定会在类被第一次访问时被创建。你得到了一个自动使用了懒汉式实例化的线程安全的实现;你应该这样使用它:

Java代码 复制代码
  1. Singleton singleton = Singleton.INSTANCE;    
  2. singleton.dothis();    
  3. singleton.dothat();    
  4. ...   
      Singleton singleton = Singleton.INSTANCE; 
      singleton.dothis(); 
      singleton.dothat(); 
      ... 



当然万事并不完美,前面的Singleton只是一个折衷的方案;如果你使用那个实现,你就无法改变它以便后来你可能想要允许多个单例类的实例。用一种更折哀的单例模式实现(通过一个getInstance()方法获得实例)你可以改变这个方法以便返回一个唯一的实例或者是数百个实例中的一个.你不能用一个公开且是静态的(public static)成员变量这样做.

你可以安全的使用例7的单例模式实现或者是例1的带一个同步的getInstance()方法的实现.然而,我们必须要研究另一个问题:你必须在编译期指定这个单例类,这样就不是很灵活.一个单例类的注册表会让我们在运行期指定一个单例类.

使用注册表
使用一个单例类注册表可以:

在运行期指定单例类

防止产生多个单例类子类的实例
在例8的单例类中,保持了一个通过类名进行注册的单例类注册表:
例8 带注册表的单例类

Java代码 复制代码
  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. }   
import java.util.HashMap; 
import org.apache.log4j.Logger; 
  
public class Singleton { 
   private static HashMap map = new HashMap(); 
   private static Logger logger = Logger.getRootLogger(); 
  
   protected Singleton() { 
      // Exists only to thwart instantiation 
   } 
   public static synchronized Singleton getInstance(String classname) { 
      if(classname == null) throw new IllegalArgumentException("Illegal classname"); 
         Singleton singleton = (Singleton)map.get(classname); 
  
      if(singleton != null) { 
         logger.info("got singleton from map: " + singleton); 
         return singleton; 
      } 
      if(classname.equals("SingeltonSubclass_One")) 
            singleton = new SingletonSubclass_One();          
         else if(classname.equals("SingeltonSubclass_Two")) 
            singleton = new SingletonSubclass_Two(); 
  
      map.put(classname, singleton); 
      logger.info("created singleton: " + singleton); 
      return singleton; 
   } 
   // Assume functionality follows that's attractive to inherit 
} 



这段代码的基类首先创建出子类的实例,然后把它们存储在一个Map中。但是基类却得付出很高的代价因为你必须为每一个子类替换它的getInstance()方法。幸运的是我们可以使用反射处理这个问题。

使用反射

在例9的带注册表的单例类中,使用反射来实例化一个特殊的类的对象。与例8相对的是通过这种实现,Singleton.getInstance()方法不需要在每个被实现的子类中重写了。
例9 使用反射实例化单例类

Java代码 复制代码
  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. <s
    分享到:
    评论

相关推荐

    Head First 设计模式 JAVA源码

    第五讲:单例模式 第六讲:原型模式 第七讲:建造者模式 第八讲:装饰模式 第九讲:策略模式 第十讲:观察者模式 第十一讲:享元模式 第十二讲:代理模式 第十三讲:外观模式 第十四讲:组合模式 第十五讲...

    Java设计模式.docx

    单例模式:单例模式是一种创建型模式,它保证一个类只有一个实例,并提供了全局访问点。 适配器模式:适配器模式是一种行为型模式,它允许将一个类的接口转换成客户端所期望的另一个接口。 观察者模式:观察者模式是...

    单例模式和CAS(CompareAndSet).md

    玩转单例模式及比较并交换CAS

    c++设计模式全系列+应用场景+实例说明

    2. **单例模式(Singleton Pattern)**:单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点。 3. **观察者模式(Observer Pattern)**:观察者模式是一种行为设计模式,定义了对象间的...

    WeNetwork:Retrofit+OkHttp+Rxjava组合的高性能的网络框架,采用单例模式,支持多BaseUrl,支持动态替换拦截器

    WeNetWork 一 框架简介: 一个OkHttp + Retrofit +Rxjava 组合的高性能、超解耦、动态处理、链式请求、单例模式的网络框架。...1.1 单例模式: WeNetWork采用的是单例模式,也就是OkHttp和Retrofit对象基本上只会创建一

    设计模式python模版

    单例模式(Singleton):确保一个类只有一个实例,并提供一个全局访问点。 工厂方法模式(Factory Method):定义一个接口用于创建对象,但让子类决定实例化哪个类。 抽象工厂模式(Abstract Factory):创建一系列...

    单例模式七种写法_转

    NULL 博文链接:https://kaka100.iteye.com/blog/1060519

    尚硅谷设计模式源码笔记课件.zip

    1) 内容包括: 设计模式七大原则(单一职责、接口隔离、依赖倒转、里氏替换、开闭原则、迪米特法则、合成复用)、UML类图(类的依赖、泛化和实现、类的关联、聚合和组合) 23种设计模式包括:创建型模式:单例模式(8种...

    设计模式:创建型之原型(深浅拷贝)、单例(饿汉式与懒汉式),三大工厂模式 结构型之适配器模式,代理模式(三大代理)

    单例模式是保证一个类仅有一个实例,并自行提供访问该实例全局访问点的创建型模式。 原型模式用来解决对象的创建问题,它是指用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的对象 ...

    浅议单例模式之线程安全(转)

    NULL 博文链接:https://javawl.iteye.com/blog/1831804

    DesignPattern

    --第五章:单例模式(Singleton) 本质:控制实例数目 --第六章:工厂方法模式(Factory Method) 本质:延迟到子类类选择实现(选择单个产品的实现) --第七章:抽象工厂模式(Abstract Factory) 本质:选择产品簇的实现 -...

    Java23种设计模式详解,看这一个就够了

    Java设计模式共有23种,分别为:抽象工厂模式、建造模式、工厂方法模式、原型模式、单例模式、外观模式、适配器模式、桥接模式、组合模式、装饰模式、享元模式、代理模式、命令模式、解释器模式、访问者模式、迭代子...

    图解Java单例模式内存分配

    1:虚拟机加载StaticDemo类,保存类型信息到方法区。  2:通过保存在方法区的字节码,虚拟机开始main方法,main方法入栈。  3:进入main方法第一条指令,Person.getInstance();首先虚拟机加载Person类到方法区...

    设计模式(23种)与设计原则(6种)

    5)单例模式(Singleton) 6)适配器模式(Adapter) 7)桥接模式(Bridge) 8)组合模式(Composite) 9)装饰模式(Decorator) 10)门面模式(Facade) 11)享元模式(Flyweight) 12)代理模式(Proxy) 13)...

    23种设计模式入门到精通详解.txt

    单例模式:某个类只能有一个实例,提供一个全局的访问点。 简单工厂:一个工厂类根据传入的参量决定创建出那一种产品类的实例。 工厂方法:定义一个创建对象的接口,让子类决定实例化那个类。 抽象工厂:创建相关...

    Java设计模式 版本2

    前言,UML建模技术,深入浅出UML类图,从招式与内功谈起——设计模式概述,面向对象设计原则,工厂三兄弟之简单工厂模式,工厂三兄弟之工厂方法模式,工厂三兄弟之抽象工厂模式,确保对象的唯一性——单例模式,...

    Delphi模式编程第一分卷

    7.3.1 一个共享数据库连接的单例模式范例 7.3.2 范例小结 第8章 原型模式(Prototype) 8.1 模式解说 8.2 结构和用法 8.2.1 模式结构 8.2.2 代码模板 8.3 范例与实践 8.3.1 Delphi对象的克隆 8.3.2 用原型...

    Delphi模式编程第二分卷

    7.3.1 一个共享数据库连接的单例模式范例 7.3.2 范例小结 第8章 原型模式(Prototype) 8.1 模式解说 8.2 结构和用法 8.2.1 模式结构 8.2.2 代码模板 8.3 范例与实践 8.3.1 Delphi对象的克隆 ...

    PHP设计模式精彩剖析

    2. 单例模式(Singleton Pattern):确保一个类只有一个实例,并提供全局访问点。 3. 观察者模式(Observer Pattern):定义了一种一对多的依赖关系,当一个对象状态改变时,所有依赖它的对象都会得到通知并更新。 ...

Global site tag (gtag.js) - Google Analytics