- 浏览: 915322 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
天天来注册:
...
try catch finally 用法 -
tadpole_java:
谢谢你的分享。
二十七、Qt数据库(七)QSqlRelationalTableModel(转) -
359449749tan:
android之EditText文本监听(addTextChangedListener) -
michael_wang:
人过留名 多谢分享
Android NOtification 使用 -
wilsonchen:
wangqi0614 写道这个删除是删除所有的把?能不能值删除 ...
Android的SharedPreferences保存与删除数据简单实例
在《设计模式》一书中,作者这样来叙述单例模式的:确保一个类只有一个实例并提供一个对它的全局访问指针。
下图说明了单例模式的类图。
(图1)
单例模式的类图
正如你在上图中所看到的,这不是单例模式的完整部分。此图中单例类保持了一个对唯一的单例实例的静态引用,并且会从静态getInstance()方法中返回对那个实例的引用。
例1显示了一个经典的单例模式的实现。
例1.经典的单例模式
- 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 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类的实例。然而,你会惊奇的发现下面的代码完全合法:
- public class SingletonInstantiator {
- public SingletonInstantiator() {
- ClassicSingleton instance = ClassicSingleton.getInstance();
- ClassicSingleton anotherInstance =
- new ClassicSingleton();
- ...
- }
- }
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.一个单例模式的案例
- 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);
- }
- }
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.是这个测试案例的输出
- 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)
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()方法由于下面的代码而不是线程安全的:
1: if(instance == null) { 2: instance = new Singleton(); 3: }
如果一个线程在第二行的赋值语句发生之前切换,那么成员变量instance仍然是null,然后另一个线程可能接下来进入到if块中。在这种情况下,两个不同的单例类实例就被创建。不幸的是这种假定很少发生,这样这种假定也很难在测试期间出现(译注:在这可能是作者对很少出现这种情况而导致无法测试从而使人们放松警惕而感到叹惜)。为了演示这个线程轮换,我得重新实现例1中的那个类。例4就是修订后的单例类:
例4.人为安排的方式
- 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; 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.失败的测试
- 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);
- }
- }
- }
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的输出
- 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)
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()方法:
- public synchronized static Singleton getInstance() {
- if(singleton == null) {
- simulateRandomActivity();
- singleton = new Singleton();
- }
- logger.info("created singleton: " + singleton);
- return singleton;
- }
public synchronized static Singleton getInstance() { if(singleton == null) { simulateRandomActivity(); singleton = new Singleton(); } logger.info("created singleton: " + singleton); return singleton; }
在同步化getInstance()方法后,我们就可以得到例5的测试案例返回的下面的结果:
- 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)
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()方法:
- 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) { singleton = new Singleton(); } } return singleton; }
这个代码片段只同步了关键的代码,而不是同步整个方法。然而这段代码却不是线程安全的。考虑一下下面的假定:线程1进入同步块,并且在它给singleton成员变量赋值之前线程1被切换。接着另一个线程进入if块。第二个线程将等待直到第一个线程完成,并且仍然会得到两个不同的单例类实例。有修复这个问题的方法吗?请读下去。
双重加锁检查
初看上去,双重加锁检查似乎是一种使懒汉式实例化为线程安全的技术。下面的代码片段展示了这种技术:
- public static Singleton getInstance() {
- if(singleton == null) {
- synchronized(Singleton.class) {
- if(singleton == null) {
- singleton = new Singleton();
- }
- }
- }
- return singleton;
- }
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.一个简单的单例类
- public class Singleton {
- public final static Singleton INSTANCE = new Singleton();
- private Singleton() {
- // Exists only to defeat instantiation.
- }
- }
public class Singleton { public final static Singleton INSTANCE = new Singleton(); private Singleton() { // Exists only to defeat instantiation. } }
这段代码是线程安全的是因为静态成员变量一定会在类被第一次访问时被创建。你得到了一个自动使用了懒汉式实例化的线程安全的实现;你应该这样使用它:
Singleton singleton = Singleton.INSTANCE; singleton.dothis(); singleton.dothat(); ...
当然万事并不完美,前面的Singleton只是一个折衷的方案;如果你使用那个实现,你就无法改变它以便后来你可能想要允许多个单例类的实例。用一种更折哀的单例模式实现(通过一个getInstance()方法获得实例)你可以改变这个方法以便返回一个唯一的实例或者是数百个实例中的一个.你不能用一个公开且是静态的(public static)成员变量这样做.
你可以安全的使用例7的单例模式实现或者是例1的带一个同步的getInstance()方法的实现.然而,我们必须要研究另一个问题:你必须在编译期指定这个单例类,这样就不是很灵活.一个单例类的注册表会让我们在运行期指定一个单例类.
使用注册表
使用一个单例类注册表可以:
在运行期指定单例类
防止产生多个单例类子类的实例
在例8的单例类中,保持了一个通过类名进行注册的单例类注册表:
例8 带注册表的单例类
- 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
- }
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 使用反射实例化单例类
- 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 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; } }
关于单例类的注册表应该说明的是:它们应该被封装在它们自己的类中以便最大限度的进行复用。
封装注册表
例10列出了一个单例注册表类。
例10 一个SingletonRegistry类
- 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 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; } }
注意我是把SingletonRegistry类作为一个单例模式实现的。我也通用化了这个注册表以便它能存储和取回任何类型的对象。例11显示了的Singleton类使用了这个注册表。
例11 使用了一个封装的注册表的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);
- }
- }
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); } }
上面的Singleton类使用那个注册表的唯一实例通过类名取得单例对象。
现在我们已经知道如何实现线程安全的单例类和如何使用一个注册表去在运行期指定单例类名,接着让我们考查一下如何安排类载入器和处理序列化。
Classloaders
在许多情况下,使用多个类载入器是很普通的--包括servlet容器--所以不管你在实现你的单例类时是多么小心你都最终可以得到多个单例类的实例。如果你想要确保你的单例类只被同一个的类载入器装入,那你就必须自己指定这个类载入器;例如:
- private static Class getClass(String classname)
- throws ClassNotFoundException {
- ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
- if(classLoader == null)
- classLoader = Singleton.class.getClassLoader();
- return (classLoader.loadClass(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)); } }
这个方法会尝试把当前的线程与那个类载入器相关联;如果classloader为null,这个方法会使用与装入单例类基类的那个类载入器。这个方法可以用Class.forName()代替。
序列化
如果你序列化一个单例类,然后两次重构它,那么你就会得到那个单例类的两个实例,除非你实现readResolve()方法,像下面这样:
例12 一个可序列化的单例类
- 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 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; } }
上面的单例类实现从readResolve()方法中返回一个唯一的实例;这样无论Singleton类何时被重构,它都只会返回那个相同的单例类实例。
例13测试了例12的单例类:
例13 测试一个可序列化的单例类
- 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);
- }
- }
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); } }
前面这个测试案例序列化例12中的单例类,并且两次重构它。然后这个测试案例检查看是否被重构的单例类实例是同一个对象。下面是测试案例的输出:
- 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)
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开发者在顾及多线程、类载入器和序列化情况如何实现单例模式。作者也展示了你怎样才能实现一个单例类的注册表,以便能够在运行期指定单例类。
发表评论
-
判断两二叉树相等
2014-05-29 00:13 1143bool IsBSTEqual(BNode* root1, ... -
数据去重
2014-05-29 00:11 858引用 使用数据结构丰富的脚本语言,如Python,利用其中的字 ... -
判断一个整数是否为2的次方幂
2014-05-28 23:56 890/* 判断一个整数是否为2的次方幂 */ bool ... -
实现Comparable接口,进行排序
2014-05-28 23:47 8830import java.util.ArrayList; ... -
Struct2详解
2014-05-21 15:14 932引用 Servlet的缺点: 1、web.xml配置比较多 2 ... -
常用方法
2014-04-25 20:24 791[color=red]String[/color] ... -
java nio和io的比较
2014-04-16 19:49 1760引用 第一部分:简单介绍NIO 服务器在合理时间内处理 ... -
Struts2、Spring3、HIbernate4总结
2014-04-16 10:35 871引用 第一部分:Struts2 1 ... -
Finally的使用总结
2014-04-15 22:02 904//清单一: public class ... -
Fibonacci数列的递归与非递归
2014-04-15 20:57 742//非递归实现 //f(1)=1,f(2)=1,f(n) ... -
Java中的异常
2014-04-14 09:42 656引用 Throwable包括两个子类:Error和Except ... -
JVM的垃圾回收机制
2014-04-13 18:39 874引用 堆被划分为新生代和旧生代, 新生代包含Eden 和 S ... -
集合类总结
2014-04-04 15:54 696引用 一. 总的框架 总的有Collection和Map Co ... -
设计模式之策略模式
2014-04-01 17:07 635main ======================= ... -
设计模式之责任链模式
2014-04-01 16:32 755main ======================= ... -
设计模式之访问者模式
2014-03-31 18:57 874main ======================= ... -
设计模式之工厂模式
2014-03-31 11:33 647main ======================= ... -
设计模式之观察者模式
2014-03-30 15:04 876main =============== ... -
Java虚拟机JVM详解
2014-03-29 12:13 1338引用 第一部分:JVM基本结构 1.什么是JVM 一个java ... -
Java中的反射机制Reflect
2014-03-28 17:17 3621package com.reflect.sym; i ...
相关推荐
单例模式是一种设计模式,旨在确保一个类只有一个实例,并提供全局访问点。在单例模式中,类的构造函数是私有的,防止外部直接创建对象,而是通过静态方法获取该类的唯一实例。单例模式的唯一性通常是在进程范围内,...
单例模式是软件设计模式中的一种,用于控制类的实例化过程,确保一个类在整个程序运行期间只有一个实例存在。在Android开发中,单例模式应用广泛,尤其在管理全局资源、提供公共服务或优化性能时非常实用。下面我们...
单例模式是软件设计模式中的一种,它的主要目的是确保一个类只有一个实例,并提供一个全局访问点。在Java或类似的面向对象编程语言中,单例模式常用于管理共享资源,如数据库连接池、线程池或者配置文件等。在这个...
Java单例模式简单示例 Java单例模式是一种非常常用的设计模式,它可以确保某个类只有一个实例,并提供一个全局访问点。下面我们将通过一个简单的示例来介绍Java单例模式的定义和使用技巧。 单例模式的定义 单例...
单例模式是软件设计模式中的一种经典模式,其主要目的是确保一个类只有一个实例,并提供一个全局访问点。这种模式在很多场景下非常有用,比如控制共享资源、管理配置对象等。下面将详细介绍七种常见的单例模式实现...
Python中的单例模式是一种设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。这种模式常用于管理共享资源,如数据库连接、配置对象等,以避免资源的重复创建和销毁,提高效率。 单例模式的...
c# 单例模式的实现方法 单例模式是所有设计模式中最简单的一种,主要用于确保整个应用程序中只有一个实例存在。c# 中实现单例模式有多种方法,本文将详细介绍饿汉式和懒汉式两种实现方法。 饿汉式 饿汉式是最简单...
单例模式是设计模式的一种,它用于确保某一个类只有一个实例存在,而且自行实例化并向系统提供这个实例。在PHP中,单例模式的实现通常涉及以下几个关键点: 首先,需要有一个用于保存类的唯一实例的静态成员变量,...
"浅谈Spring单例Bean与单例模式的区别" 本文主要介绍了Spring单例Bean与单例模式的区别,通过对比两者的定义、实现机制和应用场景,帮助读者更好地理解这两种概念的异同。 一、单例模式的定义和实现 单例模式是一...
Java单例模式是一种常见的设计模式,它在软件开发中用于控制类的实例化过程,确保在任何情况下,对于特定类,系统中只有一个实例存在。这种模式对于那些需要频繁创建和销毁的对象,或者需要全局访问的对象,如配置...
C#单例模式的应用,这里给的是C#里面一个单例模式的应用案例,面向对象过程中不可避免的需要用到单例模式,我在博客园中有详细说明单例模式怎么应用的,链接地址:...
单例模式是一种常见的设计模式,用于确保一个类只有一个实例,同时向整个系统提供这个唯一的实例。这种模式在许多场景中都十分有用,例如,在一个系统中打印机假脱机程序(PrinterSpooler)应该是唯一的,以避免打印...
单例模式常用于控制实例的创建,以确保一个类只有一个实例,并且提供一个全局的访问点。在PHP开发中,单例模式非常有用,尤其是在需要限制对象实例数量时,比如数据库连接池的创建。 单例模式的核心思想在于它封装...
C++ 单例模式的详解及实例 1.什么叫单例模式? 单例模式也称为单件模式、单子模式,可能是使用最广泛的设计模式。其意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。有很...
饿汉式 线程安全,调用效率高,但是不能延时加载。 public class Singleton { private static Singleton instance = new ...如果方法没有 synchronized,单例没有 volatile , 则线程不安全。 public class Singleton {
Java 使用静态关键字实现单例模式 在软件开发中,单例模式是一种常用的设计模式,它可以确保某个类只有一个实例,并提供了全局访问点。Java 语言提供了多种方式来实现单例模式,其中使用静态关键字是其中的一种。...
### 使用Java单例模式实现一个简单的日志记录器 #### 一、单例模式简介 单例模式是一种常用的软件设计模式,在该模式中,一个类只能创建一个实例,并且提供了一个全局访问点来访问该实例。单例模式的主要优点包括...
### JS单例模式的两种实现方案 #### 一、引言 在JavaScript中,单例模式是一种常用的软件设计模式,其主要目的是确保一个类只有一个实例,并提供一个全局访问点。这种模式广泛应用于需要频繁创建的对象,例如日志...
单例模式分析代码优化方法 单例模式是23种设计模式之一,是比较简单的一种设计模式,它的目的是无论调用多少次,都返回同一个对象,它的特点是构造器私有化。单例模式分为两种结构,一种是懒汉式的,一种是饿汉式的...
Java单例模式及实现 Java单例模式是一种常见的设计模式,确保某一个类只有一个实例,而且向这个系统提供这个实例。单例模式可以分为三种:懒汉式单例、饿汉式单例、登记式单例。 单例模式的要点 1. 某个类只能有...