原文链接:http://calmness.iteye.com/blog/60179
概要
单例模式是最简单的设计模式之一,但是对于Java的开发者来说,它却有很多缺陷。在本月的专栏中,David Geary探讨了单例模式以及在面对多线程(multithreading)、类装载器(classloaders)和序列化(serialization)时如何处理这些缺陷。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; } }
public class SingletonInstantiator { public SingletonInstantiator() { ClassicSingleton instance = ClassicSingleton.getInstance(); ClassicSingleton anotherInstance = new ClassicSingleton(); ... } }
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); } }
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)
if(instance == null) { instance = new Singleton(); }
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"); } } }
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); } } }
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)
public synchronized static Singleton getInstance() { if(singleton == null) { simulateRandomActivity(); singleton = new Singleton(); } logger.info("created singleton: " + singleton); return singleton; }
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)
public static Singleton getInstance() { if(singleton == null) { synchronized(Singleton.class) { singleton = new Singleton(); } } return singleton; }
public static Singleton getInstance() { if(singleton == null) { synchronized(Singleton.class) { if(singleton == null) { singleton = new Singleton(); } } } return singleton; }
public class Singleton { public final static Singleton INSTANCE = new Singleton(); private Singleton() { // Exists only to defeat instantiation. } }
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) { Singleton singleton = (Singleton)map.get(classname); if(singleton != null) { logger.info("got singleton from map: " + singleton); return singleton; } try { singleton = (Singleton)Class.forName(classname).newInstance(); } catch(ClassNotFoundException cnf) { logger.fatal("Couldn't find class " + classname); } catch(InstantiationException ie) { logger.fatal("Couldn't instantiate an object of type " + classname); } catch(IllegalAccessException ia) { logger.fatal("Couldn't access class " + classname); } map.put(classname, singleton); logger.info("created singleton: " + singleton); return singleton; } }
import java.util.HashMap; import org.apache.log4j.Logger; public class SingletonRegistry { public static SingletonRegistry REGISTRY = new SingletonRegistry(); private static HashMap map = new HashMap(); private static Logger logger = Logger.getRootLogger(); protected SingletonRegistry() { // Exists to defeat instantiation } public static synchronized Object getInstance(String classname) { Object singleton = map.get(classname); if(singleton != null) { return singleton; } try { singleton = Class.forName(classname).newInstance(); logger.info("created singleton: " + singleton); } catch(ClassNotFoundException cnf) { logger.fatal("Couldn't find class " + classname); } catch(InstantiationException ie) { logger.fatal("Couldn't instantiate an object of type " + classname); } catch(IllegalAccessException ia) { logger.fatal("Couldn't access class " + classname); } map.put(classname, singleton); return singleton; } }
import java.util.HashMap; import org.apache.log4j.Logger; public class Singleton { protected Singleton() { // Exists only to thwart instantiation. } public static Singleton getInstance() { return (Singleton)SingletonRegistry.REGISTRY.getInstance(classname); } }
private static Class getClass(String classname) throws ClassNotFoundException { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if(classLoader == null) classLoader = Singleton.class.getClassLoader(); return (classLoader.loadClass(classname)); } }
import org.apache.log4j.Logger; public class Singleton implements java.io.Serializable { public static Singleton INSTANCE = new Singleton(); protected Singleton() { // Exists only to thwart instantiation. } private Object readResolve() { return INSTANCE; } }
import java.io.*; import org.apache.log4j.Logger; import junit.framework.Assert; import junit.framework.TestCase; public class SingletonTest extends TestCase { private Singleton sone = null, stwo = null; private static Logger logger = Logger.getRootLogger(); public SingletonTest(String name) { super(name); } public void setUp() { sone = Singleton.INSTANCE; stwo = Singleton.INSTANCE; } public void testSerialize() { logger.info("testing singleton serialization..."); [b] writeSingleton(); Singleton s1 = readSingleton(); Singleton s2 = readSingleton(); Assert.assertEquals(true, s1 == s2);[/b] } private void writeSingleton() { try { FileOutputStream fos = new FileOutputStream("serializedSingleton"); ObjectOutputStream oos = new ObjectOutputStream(fos); Singleton s = Singleton.INSTANCE; oos.writeObject(Singleton.INSTANCE); oos.flush(); } catch(NotSerializableException se) { logger.fatal("Not Serializable Exception: " + se.getMessage()); } catch(IOException iox) { logger.fatal("IO Exception: " + iox.getMessage()); } } private Singleton readSingleton() { Singleton s = null; try { FileInputStream fis = new FileInputStream("serializedSingleton"); ObjectInputStream ois = new ObjectInputStream(fis); s = (Singleton)ois.readObject(); } catch(ClassNotFoundException cnf) { logger.fatal("Class Not Found Exception: " + cnf.getMessage()); } catch(NotSerializableException se) { logger.fatal("Not Serializable Exception: " + se.getMessage()); } catch(IOException iox) { logger.fatal("IO Exception: " + iox.getMessage()); } return s; } public void testUnique() { logger.info("testing singleton uniqueness..."); Singleton another = new Singleton(); logger.info("checking singletons for equality"); Assert.assertEquals(true, sone == stwo); } }
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)
单例模式简单却容易让人迷惑,特别是对于Java的开发者来说。在这篇文章中,作者演示了Java开发者在顾及多线程、类载入器和序列化情况如何实现单例模式。作者也展示了你怎样才能实现一个单例类的注册表,以便能够在运行期指定单例类。