单例模式与多线程

yuxia82612 贡献于2012-08-20

作者   创建于2009-03-03 14:31:29   修改者  修改于1899-12-30 00:00:00字数16561

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

概要 单例模式是最简单的设计模式之一,但是对于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 } 在例1中的单例模式的实现很容易理解。ClassicSingleton类保持了一个对单独的单例实例的静态引用,并且从静态方法getInstance()中返回那个引用。 关于ClassicSingleton类,有几个让我们感兴趣的地方。首先,ClassicSingleton使用了一个众所周知的懒汉式实例化去创建那个单例类的引用;结果,这个单例类的实例直到getInstance()方法被第一次调用时才被创建。这种技巧可以确保单例类的实例只有在需要时才被建立出来。其次,注意ClassicSingleton实现了一个protected的构造方法,这样客户端不能直接实例化一个ClassicSingleton类的实例。然而,你会惊奇的发现下面的代码完全合法: Java代码 14 public class SingletonInstantiator { 15 public SingletonInstantiator() { 16 ClassicSingleton instance = ClassicSingleton.getInstance(); 17 ClassicSingleton anotherInstance = 18 new ClassicSingleton(); 19 ... 20 } 21 } 前面这个代码片段为何能在没有继承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代码 22 import org.apache.log4j.Logger; 23 import junit.framework.Assert; 24 import junit.framework.TestCase; 25 26 public class SingletonTest extends TestCase { 27 private ClassicSingleton sone = null, stwo = null; 28 private static Logger logger = Logger.getRootLogger(); 29 30 public SingletonTest(String name) { 31 super(name); 32 } 33 public void setUp() { 34 logger.info("getting singleton..."); 35 sone = ClassicSingleton.getInstance(); 36 logger.info("...got singleton: " + sone); 37 38 logger.info("getting singleton..."); 39 stwo = ClassicSingleton.getInstance(); 40 logger.info("...got singleton: " + stwo); 41 } 42 public void testUnique() { 43 logger.info("checking singletons for equality"); 44 Assert.assertEquals(true, sone == stwo); 45 } 46 } 例2两次调用ClassicSingleton.getInstance(),并且把返回的引用存储在成员变量中。方法testUnique()会检查这些引用看它们是否相同。例3是这个测试案例的输出: 例3.是这个测试案例的输出 Java代码 47 Buildfile: build.xml 48 49 init: 50 [echo] Build 20030414 (14-04-2003 03:08) 51 52 compile: 53 54 run-test-text: 55 [java] .INFO main: [b]getting singleton...[/b] 56 [java] INFO main: [b]created singleton:[/b] Singleton@e86f41 57 [java] INFO main: ...got singleton: Singleton@e86f41 58 [java] INFO main: [b]getting singleton...[/b] 59 [java] INFO main: ...got singleton: Singleton@e86f41 60 [java] INFO main: checking singletons for equality 61 62 [java] Time: 0.032 63 64 [java] OK (1 test) 正如前面的清单所示,例2的简单测试顺利通过----通过ClassicSingleton.getInstance()获得的两个单例类的引用确实相同;然而,你要知道这些引用是在单线程中得到的。下面的部分着重于用多线程测试单例类。 多线程因素的考虑 在例1中的ClassicSingleton.getInstance()方法由于下面的代码而不是线程安全的: Java代码 65 1: if(instance == null) { 66 2: instance = new Singleton(); 67 3: } 如果一个线程在第二行的赋值语句发生之前切换,那么成员变量instance仍然是null,然后另一个线程可能接下来进入到if块中。在这种情况下,两个不同的单例类实例就被创建。不幸的是这种假定很少发生,这样这种假定也很难在测试期间出现(译注:在这可能是作者对很少出现这种情况而导致无法测试从而使人们放松警惕而感到叹惜)。为了演示这个线程轮换,我得重新实现例1中的那个类。例4就是修订后的单例类: 例4.人为安排的方式 Java代码 68 import org.apache.log4j.Logger; 69 70 public class Singleton { 71 private static Singleton singleton = null; 72 private static Logger logger = Logger.getRootLogger(); 73 private static boolean firstThread = true; 74 75 protected Singleton() { 76 // Exists only to defeat instantiation. 77 } 78 public static Singleton getInstance() { 79 if(singleton == null) { 80 simulateRandomActivity(); 81 singleton = new Singleton(); 82 } 83 logger.info("created singleton: " + singleton); 84 return singleton; 85 } 86 private static void simulateRandomActivity() { 87 try { 88 if(firstThread) { 89 firstThread = false; 90 logger.info("sleeping..."); 91 92 // This nap should give the second thread enough time 93 // to get by the first thread. 94 Thread.currentThread().sleep(50); 95 } 96 } 97 catch(InterruptedException ex) { 98 logger.warn("Sleep interrupted"); 99 } 100 } 101 } 除了在这个清单中的单例类强制使用了一个多线程错误处理,例4类似于例1中的单例类。在getInstance()方法第一次被调用时,调用这个方法的线程会休眠50毫秒以便另外的线程也有时间调用getInstance()并创建一个新的单例类实例。当休眠的线程觉醒时,它也会创建一个新的单例类实例,这样我们就有两个单例类实例。尽管例4是人为如此的,但它却模拟了第一个线程调用了getInstance()并在没有完成时被切换的真实情形。 例5测试了例4的单例类: 例5.失败的测试 Java代码 102 import org.apache.log4j.Logger; 103 import junit.framework.Assert; 104 import junit.framework.TestCase; 105 106 public class SingletonTest extends TestCase { 107 private static Logger logger = Logger.getRootLogger(); 108 private static Singleton singleton = null; 109 110 public SingletonTest(String name) { 111 super(name); 112 } 113 public void setUp() { 114 singleton = null; 115 } 116 public void testUnique() throws InterruptedException { 117 // Both threads call Singleton.getInstance(). 118 Thread threadOne = new Thread(new SingletonTestRunnable()), 119 threadTwo = new Thread(new SingletonTestRunnable()); 120 121 threadOne.start(); 122 threadTwo.start(); 123 124 threadOne.join(); 125 threadTwo.join(); 126 } 127 private static class SingletonTestRunnable implements Runnable { 128 public void run() { 129 // Get a reference to the singleton. 130 Singleton s = Singleton.getInstance(); 131 132 // Protect singleton member variable from 133 // multithreaded access. 134 synchronized(SingletonTest.class) { 135 if(singleton == null) // If local reference is null... 136 singleton = s; // ...set it to the singleton 137 } 138 // Local reference must be equal to the one and 139 // only instance of Singleton; otherwise, we have two 140 // Singleton instances. 141 Assert.assertEquals(true, s == singleton); 142 } 143 } 144 } 例5的测试案例创建两个线程,然后各自启动,等待完成。这个案例保持了一个对单例类的静态引用,每个线程都会调用Singleton.getInstance()。如果这个静态成员变量没有被设置,那么第一个线程就会将它设为通过调用getInstance()而得到的引用,然后这个静态变量会与一个局部变量比较是否相等。 在这个测试案例运行时会发生一系列的事情:第一个线程调用getInstance(),进入if块,然后休眠;接着,第二个线程也调用getInstance()并且创建了一个单例类的实例。第二个线程会设置这个静态成员变量为它所创建的引用。第二个线程检查这个静态成员变量与一个局部备份的相等性。然后测试通过。当第一个线程觉醒时,它也会创建一个单例类的实例,并且它不会设置那个静态成员变量(因为第二个线程已经设置过了),所以那个静态变量与那个局部变量脱离同步,相等性测试即告失败。例6列出了例5的输出: 例6.例5的输出 Java代码 145 Buildfile: build.xml 146 init: 147 [echo] Build 20030414 (14-04-2003 03:06) 148 compile: 149 run-test-text: 150 INFO Thread-1: sleeping... 151 INFO Thread-2: created singleton: Singleton@7e5cbd 152 INFO Thread-1: created singleton: Singleton@704ebb 153 junit.framework.AssertionFailedError: expected: but was: 154 at junit.framework.Assert.fail(Assert.java:47) 155 at junit.framework.Assert.failNotEquals(Assert.java:282) 156 at junit.framework.Assert.assertEquals(Assert.java:64) 157 at junit.framework.Assert.assertEquals(Assert.java:149) 158 at junit.framework.Assert.assertEquals(Assert.java:155) 159 at SingletonTest$SingletonTestRunnable.run(Unknown Source) 160 at java.lang.Thread.run(Thread.java:554) 161 [java] . 162 [java] Time: 0.577 163 164 [java] OK (1 test) 到现在为止我们已经知道例4不是线程安全的,那就让我们看看如何修正它。 同步 要使例4的单例类为线程安全的很容易----只要像下面一个同步化getInstance()方法: Java代码 165 public synchronized static Singleton getInstance() { 166 if(singleton == null) { 167 simulateRandomActivity(); 168 singleton = new Singleton(); 169 } 170 logger.info("created singleton: " + singleton); 171 return singleton; 172 } 在同步化getInstance()方法后,我们就可以得到例5的测试案例返回的下面的结果: Java代码 173 Buildfile: build.xml 174 175 init: 176 [echo] Build 20030414 (14-04-2003 03:15) 177 178 compile: 179 [javac] Compiling 2 source files 180 181 run-test-text: 182 INFO Thread-1: sleeping... 183 INFO Thread-1: created singleton: Singleton@ef577d 184 INFO Thread-2: created singleton: Singleton@ef577d 185 [java] . 186 [java] Time: 0.513 187 188 [java] OK (1 test) 这此,这个测试案例工作正常,并且多线程的烦恼也被解决;然而,机敏的读者可能会认识到getInstance()方法只需要在第一次被调用时同步。因为同步的性能开销很昂贵(同步方法比非同步方法能降低到100次左右),或许我们可以引入一种性能改进方法,它只同步单例类的getInstance()方法中的赋值语句。 一种性能改进的方法 寻找一种性能改进方法时,你可能会选择像下面这样重写getInstance()方法: Java代码 189 public static Singleton getInstance() { 190 if(singleton == null) { 191 synchronized(Singleton.class) { 192 singleton = new Singleton(); 193 } 194 } 195 return singleton; 196 } 这个代码片段只同步了关键的代码,而不是同步整个方法。然而这段代码却不是线程安全的。考虑一下下面的假定:线程1进入同步块,并且在它给singleton成员变量赋值之前线程1被切换。接着另一个线程进入if块。第二个线程将等待直到第一个线程完成,并且仍然会得到两个不同的单例类实例。有修复这个问题的方法吗?请读下去。 双重加锁检查 初看上去,双重加锁检查似乎是一种使懒汉式实例化为线程安全的技术。下面的代码片段展示了这种技术: Java代码 197 public static Singleton getInstance() { 198 if(singleton == null) { 199 synchronized(Singleton.class) { 200 if(singleton == null) { 201 singleton = new Singleton(); 202 } 203 } 204 } 205 return singleton; 206 } 如果两个线程同时访问getInstance()方法会发生什么?想像一下线程1进行同步块马上又被切换。接着,第二个线程进入if 块。当线程1退出同步块时,线程2会重新检查看是否singleton实例仍然为null。因为线程1设置了singleton成员变量,所以线程2的第二次检查会失败,第二个单例类实例也就不会被创建。似乎就是如此。 不幸的是,双重加锁检查不会保证正常工作,因为编译器会在Singleton的构造方法被调用之前随意给singleton赋一个值。如果在singleton引用被赋值之后而被初始化之前线程1被切换,线程2就会被返回一个对未初始化的单例类实例的引用。 一个改进的线程安全的单例模式实现 例7列出了一个简单、快速而又是线程安全的单例模式实现: 例7.一个简单的单例类 Java代码 207 public class Singleton { 208 public final static Singleton INSTANCE = new Singleton(); 209 private Singleton() { 210 // Exists only to defeat instantiation. 211 } 212 } 这段代码是线程安全的是因为静态成员变量一定会在类被第一次访问时被创建。你得到了一个自动使用了懒汉式实例化的线程安全的实现;你应该这样使用它: Java代码 213 Singleton singleton = Singleton.INSTANCE; 214 singleton.dothis(); 215 singleton.dothat(); 216 ... 当然万事并不完美,前面的Singleton只是一个折衷的方案;如果你使用那个实现,你就无法改变它以便后来你可能想要允许多个单例类的实例。用一种更折哀的单例模式实现(通过一个getInstance()方法获得实例)你可以改变这个方法以便返回一个唯一的实例或者是数百个实例中的一个.你不能用一个公开且是静态的(public static)成员变量这样做. 你可以安全的使用例7的单例模式实现或者是例1的带一个同步的getInstance()方法的实现.然而,我们必须要研究另一个问题:你必须在编译期指定这个单例类,这样就不是很灵活.一个单例类的注册表会让我们在运行期指定一个单例类. 使用注册表 使用一个单例类注册表可以: 在运行期指定单例类 防止产生多个单例类子类的实例 在例8的单例类中,保持了一个通过类名进行注册的单例类注册表: 例8 带注册表的单例类 Java代码 217 import java.util.HashMap; 218 import org.apache.log4j.Logger; 219 220 public class Singleton { 221 private static HashMap map = new HashMap(); 222 private static Logger logger = Logger.getRootLogger(); 223 224 protected Singleton() { 225 // Exists only to thwart instantiation 226 } 227 public static synchronized Singleton getInstance(String classname) { 228 if(classname == null) throw new IllegalArgumentException("Illegal classname"); 229 Singleton singleton = (Singleton)map.get(classname); 230 231 if(singleton != null) { 232 logger.info("got singleton from map: " + singleton); 233 return singleton; 234 } 235 if(classname.equals("SingeltonSubclass_One")) 236 singleton = new SingletonSubclass_One(); 237 else if(classname.equals("SingeltonSubclass_Two")) 238 singleton = new SingletonSubclass_Two(); 239 240 map.put(classname, singleton); 241 logger.info("created singleton: " + singleton); 242 return singleton; 243 } 244 // Assume functionality follows that's attractive to inherit 245 } 这段代码的基类首先创建出子类的实例,然后把它们存储在一个Map中。但是基类却得付出很高的代价因为你必须为每一个子类替换它的getInstance()方法。幸运的是我们可以使用反射处理这个问题。 使用反射 在例9的带注册表的单例类中,使用反射来实例化一个特殊的类的对象。与例8相对的是通过这种实现,Singleton.getInstance()方法不需要在每个被实现的子类中重写了。 例9 使用反射实例化单例类 Java代码 246 import java.util.HashMap; 247 import org.apache.log4j.Logger; 248 249 public class Singleton { 250 private static HashMap map = new HashMap(); 251 private static Logger logger = Logger.getRootLogger(); 252 253 protected Singleton() { 254 // Exists only to thwart instantiation 255 } 256 public static synchronized Singleton getInstance(String classname) { 257 Singleton singleton = (Singleton)map.get(classname); 258 259 if(singleton != null) { 260 logger.info("got singleton from map: " + singleton); 261 return singleton; 262 } 263 try { 264 singleton = (Singleton)Class.forName(classname).newInstance(); 265 } 266 catch(ClassNotFoundException cnf) { 267 logger.fatal("Couldn't find class " + classname); 268 } 269 catch(InstantiationException ie) { 270 logger.fatal("Couldn't instantiate an object of type " + classname); 271 } 272 catch(IllegalAccessException ia) { 273 logger.fatal("Couldn't access class " + classname); 274 } 275 map.put(classname, singleton); 276 logger.info("created singleton: " + singleton); 277 278 return singleton; 279 } 280 } 关于单例类的注册表应该说明的是:它们应该被封装在它们自己的类中以便最大限度的进行复用。 封装注册表 例10列出了一个单例注册表类。 例10 一个SingletonRegistry类 Java代码 281 import java.util.HashMap; 282 import org.apache.log4j.Logger; 283 284 public class SingletonRegistry { 285 public static SingletonRegistry REGISTRY = new SingletonRegistry(); 286 287 private static HashMap map = new HashMap(); 288 private static Logger logger = Logger.getRootLogger(); 289 290 protected SingletonRegistry() { 291 // Exists to defeat instantiation 292 } 293 public static synchronized Object getInstance(String classname) { 294 Object singleton = map.get(classname); 295 296 if(singleton != null) { 297 return singleton; 298 } 299 try { 300 singleton = Class.forName(classname).newInstance(); 301 logger.info("created singleton: " + singleton); 302 } 303 catch(ClassNotFoundException cnf) { 304 logger.fatal("Couldn't find class " + classname); 305 } 306 catch(InstantiationException ie) { 307 logger.fatal("Couldn't instantiate an object of type " + 308 classname); 309 } 310 catch(IllegalAccessException ia) { 311 logger.fatal("Couldn't access class " + classname); 312 } 313 map.put(classname, singleton); 314 return singleton; 315 } 316 } 注意我是把SingletonRegistry类作为一个单例模式实现的。我也通用化了这个注册表以便它能存储和取回任何类型的对象。例11显示了的Singleton类使用了这个注册表。 例11 使用了一个封装的注册表的Singleton类 Java代码 317 import java.util.HashMap; 318 import org.apache.log4j.Logger; 319 320 public class Singleton { 321 322 protected Singleton() { 323 // Exists only to thwart instantiation. 324 } 325 public static Singleton getInstance() { 326 return (Singleton)SingletonRegistry.REGISTRY.getInstance(classname); 327 } 328 } 上面的Singleton类使用那个注册表的唯一实例通过类名取得单例对象。 现在我们已经知道如何实现线程安全的单例类和如何使用一个注册表去在运行期指定单例类名,接着让我们考查一下如何安排类载入器和处理序列化。 Classloaders 在许多情况下,使用多个类载入器是很普通的--包括servlet容器--所以不管你在实现你的单例类时是多么小心你都最终可以得到多个单例类的实例。如果你想要确保你的单例类只被同一个的类载入器装入,那你就必须自己指定这个类载入器;例如: Java代码 329 private static Class getClass(String classname) 330 throws ClassNotFoundException { 331 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 332 333 if(classLoader == null) 334 classLoader = Singleton.class.getClassLoader(); 335 336 return (classLoader.loadClass(classname)); 337 } 338 } 这个方法会尝试把当前的线程与那个类载入器相关联;如果classloader为null,这个方法会使用与装入单例类基类的那个类载入器。这个方法可以用Class.forName()代替。 序列化 如果你序列化一个单例类,然后两次重构它,那么你就会得到那个单例类的两个实例,除非你实现readResolve()方法,像下面这样: 例12 一个可序列化的单例类 Java代码 339 import org.apache.log4j.Logger; 340 341 public class Singleton implements java.io.Serializable { 342 public static Singleton INSTANCE = new Singleton(); 343 344 protected Singleton() { 345 // Exists only to thwart instantiation. 346 } 347 private Object readResolve() { 348 return INSTANCE; 349 } 350 } 上面的单例类实现从readResolve()方法中返回一个唯一的实例;这样无论Singleton类何时被重构,它都只会返回那个相同的单例类实例。 例13测试了例12的单例类: 例13 测试一个可序列化的单例类 Java代码 351 import java.io.*; 352 import org.apache.log4j.Logger; 353 import junit.framework.Assert; 354 import junit.framework.TestCase; 355 356 public class SingletonTest extends TestCase { 357 private Singleton sone = null, stwo = null; 358 private static Logger logger = Logger.getRootLogger(); 359 360 public SingletonTest(String name) { 361 super(name); 362 } 363 public void setUp() { 364 sone = Singleton.INSTANCE; 365 stwo = Singleton.INSTANCE; 366 } 367 public void testSerialize() { 368 logger.info("testing singleton serialization..."); 369 [b] writeSingleton(); 370 Singleton s1 = readSingleton(); 371 Singleton s2 = readSingleton(); 372 Assert.assertEquals(true, s1 == s2);[/b] } 373 private void writeSingleton() { 374 try { 375 FileOutputStream fos = new FileOutputStream("serializedSingleton"); 376 ObjectOutputStream oos = new ObjectOutputStream(fos); 377 Singleton s = Singleton.INSTANCE; 378 379 oos.writeObject(Singleton.INSTANCE); 380 oos.flush(); 381 } 382 catch(NotSerializableException se) { 383 logger.fatal("Not Serializable Exception: " + se.getMessage()); 384 } 385 catch(IOException iox) { 386 logger.fatal("IO Exception: " + iox.getMessage()); 387 } 388 } 389 private Singleton readSingleton() { 390 Singleton s = null; 391 392 try { 393 FileInputStream fis = new FileInputStream("serializedSingleton"); 394 ObjectInputStream ois = new ObjectInputStream(fis); 395 s = (Singleton)ois.readObject(); 396 } 397 catch(ClassNotFoundException cnf) { 398 logger.fatal("Class Not Found Exception: " + cnf.getMessage()); 399 } 400 catch(NotSerializableException se) { 401 logger.fatal("Not Serializable Exception: " + se.getMessage()); 402 } 403 catch(IOException iox) { 404 logger.fatal("IO Exception: " + iox.getMessage()); 405 } 406 return s; 407 } 408 public void testUnique() { 409 logger.info("testing singleton uniqueness..."); 410 Singleton another = new Singleton(); 411 412 logger.info("checking singletons for equality"); 413 Assert.assertEquals(true, sone == stwo); 414 } 415 } 前面这个测试案例序列化例12中的单例类,并且两次重构它。然后这个测试案例检查看是否被重构的单例类实例是同一个对象。下面是测试案例的输出: Java代码 416 Buildfile: build.xml 417 418 init: 419 [echo] Build 20030422 (22-04-2003 11:32) 420 421 compile: 422 423 run-test-text: 424 [java] .INFO main: testing singleton serialization... 425 [java] .INFO main: testing singleton uniqueness... 426 [java] INFO main: checking singletons for equality 427 428 [java] Time: 0.1 429 430 [java] OK (2 tests) 单例模式结束语 单例模式简单却容易让人迷惑,特别是对于Java的开发者来说。在这篇文章中,作者演示了Java开发者在顾及多线程、类载入器和序列化情况如何实现单例模式。作者也展示了你怎样才能实现一个单例类的注册表,以便能够在运行期指定单例类。

下载文档到电脑,查找使用更方便

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 5 金币 [ 分享文档获得金币 ] 2 人已下载

下载文档