




已阅读5页,还剩15页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
猎头职位: 北京: ITeye网站诚聘产品交互设计 相关文章: DesignPattern学习-Singleton java单例模式 java单例模式(Singleton pattern) 单例模式完全剖析 推荐群组: D语言 更多相关推荐 概要 单例模式是最简单的设计模式之一,但是对于Java的开发者来说,它却有很多缺陷。在本月的专栏中,David Geary探讨了单例模式以及在面对多线程(multithreading)、类装载器(classloaders)和序列化(serialization)时如何处理这些缺陷。 单例模式适合于一个类只有一个实例的情况,比如窗口管理器,打印缓冲池和文件系统,它们都是原型的例子。典型的情况是,那些对象的类型被遍及一个软件系统的不同对象访问,因此需要一个全局的访问指针,这便是众所周知的单例模式的应用。当然这只有在你确信你不再需要任何多于一个的实例的情况下。 单例模式的用意在于前一段中所关心的。通过单例模式你可以: 确保一个类只有一个实例被建立 提供了一个对对象的全局访问指针 在不影响单例类的客户端的情况下允许将来有多个实例 尽管单例设计模式如在下面的图中的所显示的一样是最简单的设计模式,但对于粗心的Java开发者来说却呈现出许多缺陷。这篇文章讨论了单例模式并揭示了那些缺陷。 注意:你可以从Resources下载这篇文章的源代码。 单例模式 在设计模式一书中,作者这样来叙述单例模式的:确保一个类只有一个实例并提供一个对它的全局访问指针。 下图说明了单例模式的类图。 (图1) 单例模式的类图 正如你在上图中所看到的,这不是单例模式的完整部分。此图中单例类保持了一个对唯一的单例实例的静态引用,并且会从静态getInstance()方法中返回对那个实例的引用。 例1显示了一个经典的单例模式的实现。 例1.经典的单例模式 Java代码 1. publicclassClassicSingleton 2. privatestaticClassicSingletoninstance=null; 3. 4. protectedClassicSingleton() 5. /Existsonlytodefeatinstantiation. 6. 7. publicstaticClassicSingletongetInstance() 8. if(instance=null) 9. instance=newClassicSingleton(); 10. 11. returninstance; 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. publicclassSingletonInstantiator 2. publicSingletonInstantiator() 3. ClassicSingletoninstance=ClassicSingleton.getInstance(); 4. ClassicSingletonanotherInstance= 5. newClassicSingleton(); 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. importorg.apache.log4j.Logger; 2. importjunit.framework.Assert; 3. importjunit.framework.TestCase; 4. 5. publicclassSingletonTestextendsTestCase 6. privateClassicSingletonsone=null,stwo=null; 7. privatestaticLoggerlogger=Logger.getRootLogger(); 8. 9. publicSingletonTest(Stringname) 10. super(name); 11. 12. publicvoidsetUp() 13. (gettingsingleton.); 14. sone=ClassicSingleton.getInstance(); 15. (.gotsingleton:+sone); 16. 17. (gettingsingleton.); 18. stwo=ClassicSingleton.getInstance(); 19. (.gotsingleton:+stwo); 20. 21. publicvoidtestUnique() 22. (checkingsingletonsforequality); 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() (getting singleton.); sone = ClassicSingleton.getInstance(); (.got singleton: + sone); (getting singleton.); stwo = ClassicSingleton.getInstance(); (.got singleton: + stwo); public void testUnique() (checking singletons for equality); Assert.assertEquals(true, sone = stwo); 例2两次调用ClassicSingleton.getInstance(),并且把返回的引用存储在成员变量中。方法testUnique()会检查这些引用看它们是否相同。例3是这个测试案例的输出: 例3.是这个测试案例的输出 Java代码 1. Buildfile:build.xml 2. 3. init: 4. echoBuild20030414(14-04-200303:08) 5. 6. compile: 7. 8. run-test-text: 9. java.INFOmain:bgettingsingleton./b 10. javaINFOmain:bcreatedsingleton:/bSingletone86f41 11. javaINFOmain:.gotsingleton:Singletone86f41 12. javaINFOmain:bgettingsingleton./b 13. javaINFOmain:.gotsingleton:Singletone86f41 14. javaINFOmain:checkingsingletonsforequality 15. 16. javaTime:0.032 17. 18. javaOK(1test)Buildfile: build.xml init: echo Build 20030414 (14-04-2003 03:08) compile: run-test-text: java .INFO main: bgetting singleton./b java INFO main: bcreated singleton:/b Singletone86f41 java INFO main: .got singleton: Singletone86f41 java INFO main: bgetting singleton./b java INFO main: .got singleton: Singletone86f41 java INFO main: checking singletons for equality java Time: 0.032 java OK (1 test)正如前面的清单所示,例2的简单测试顺利通过-通过ClassicSingleton.getInstance()获得的两个单例类的引用确实相同;然而,你要知道这些引用是在单线程中得到的。下面的部分着重于用多线程测试单例类。 多线程因素的考虑 在例1中的ClassicSingleton.getInstance()方法由于下面的代码而不是线程安全的: Java代码 1. 1:if(instance=null) 2. 2:instance=newSingleton(); 3. 3:1: if(instance = null) 2: instance = new Singleton(); 3: 如果一个线程在第二行的赋值语句发生之前切换,那么成员变量instance仍然是null,然后另一个线程可能接下来进入到if块中。在这种情况下,两个不同的单例类实例就被创建。不幸的是这种假定很少发生,这样这种假定也很难在测试期间出现(译注:在这可能是作者对很少出现这种情况而导致无法测试从而使人们放松警惕而感到叹惜)。为了演示这个线程轮换,我得重新实现例1中的那个类。例4就是修订后的单例类: 例4.人为安排的方式 Java代码 1. importorg.apache.log4j.Logger; 2. 3. publicclassSingleton 4. privatestaticSingletonsingleton=null; 5. privatestaticLoggerlogger=Logger.getRootLogger(); 6. privatestaticbooleanfirstThread=true; 7. 8. protectedSingleton() 9. /Existsonlytodefeatinstantiation. 10. 11. publicstaticSingletongetInstance() 12. if(singleton=null) 13. simulateRandomActivity(); 14. singleton=newSingleton(); 15. 16. (createdsingleton:+singleton); 17. returnsingleton; 18. 19. privatestaticvoidsimulateRandomActivity() 20. try 21. if(firstThread) 22. firstThread=false; 23. (sleeping.); 24. 25. /Thisnapshouldgivethesecondthreadenoughtime 26. /togetbythefirstthread. 27. Thread.currentThread().sleep(50); 28. 29. 30. catch(InterruptedExceptionex) 31. logger.warn(Sleepinterrupted); 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(); (created singleton: + singleton); return singleton; private static void simulateRandomActivity() try if(firstThread) firstThread = false; (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. importorg.apache.log4j.Logger; 2. importjunit.framework.Assert; 3. importjunit.framework.TestCase; 4. 5. publicclassSingletonTestextendsTestCase 6. privatestaticLoggerlogger=Logger.getRootLogger(); 7. privatestaticSingletonsingleton=null; 8. 9. publicSingletonTest(Stringname) 10. super(name); 11. 12. publicvoidsetUp() 13. singleton=null; 14. 15. publicvoidtestUnique()throwsInterruptedException 16. /BoththreadscallSingleton.getInstance(). 17. ThreadthreadOne=newThread(newSingletonTestRunnable(), 18. threadTwo=newThread(newSingletonTestRunnable(); 19. 20. threadOne.start(); 21. threadTwo.start(); 22. 23. threadOne.join(); 24. threadTwo.join(); 25. 26. privatestaticclassSingletonTestRunnableimplementsRunnable 27. publicvoidrun() 28. /Getareferencetothesingleton. 29. Singletons=Singleton.getInstance(); 30. 31. /Protectsingletonmembervariablefrom 32. /multithreadedaccess. 33. synchronized(SingletonTest.class) 34. if(singleton=null)/Iflocalreferenceisnull. 35. singleton=s;/.setittothesingleton 36. 37. /Localreferencemustbeequaltotheoneand 38. /onlyinstanceofSingleton;otherwise,wehavetwo 39. /Singletoninstances. 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. echoBuild20030414(14-04-200303:06) 4. compile: 5. run-test-text: 6. INFOThread-1:sleeping. 7. INFOThread-2:createdsingleton:Singleton7e5cbd 8. INFOThread-1:createdsingleton:Singleton704ebb 9. junit.framework.AssertionFailedError:expected:butwas: 10. atjunit.framework.Assert.fail(Assert.java:47) 11. atjunit.framework.Assert.failNotEquals(Assert.java:282) 12. atjunit.framework.Assert.assertEquals(Assert.java:64) 13. atjunit.framework.Assert.assertEquals(Assert.java:149) 14. atjunit.framework.Assert.assertEquals(Assert.java:155) 15. atSingletonTest$SingletonTestRunnable.run(UnknownSource) 16. atjava.lang.Thread.run(Thread.java:554) 17. java. 18. javaTime:0.577 19. 20. javaOK(1test)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: Singleton7e5cbd INFO Thread-1: created singleton: Singleton704ebb 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. publicsynchronizedstaticSingletongetInstance() 2. if(singleton=null) 3. simulateRandomActivity(); 4. singleton=newSingleton(); 5. 6. (createdsingleton:+singleton); 7. returnsingleton; 8. public synchronized static Singleton getInstance() if(singleton = null) simulateRandomActivity(); singleton = new Singleton(); (created singleton: + singleton); return singleton; 在同步化getInstance()方法后,我们就可以得到例5的测试案例返回的下面的结果: Java代码 1. Buildfile:build.xml 2. 3. init: 4. echoBuild20030414(14-04-200303:15) 5. 6. compile: 7. javacCompiling2sourcefiles 8. 9. run-test-text: 10. INFOThread-1:sleeping. 11. INFOThread-1:createdsingleton:Singletonef577d 12. INFOThread-2:createdsingleton:Singletonef577d 13. java. 14. javaTime:0.513 15. 16. javaOK(1test)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: Singletonef577d INFO Thread-2: created singleton: Singletonef577d java . java Time: 0.513 java OK (1 test) 这此,这个测试案例工作正常,并且多线程的烦恼也被解决;然而,机敏的读者可能会认识到getInstance()方法只需要在第一次被调用时同步。因为同步的性能开销很昂贵(同步方法比非同步方法能降低到100次左右),或许我们可以引入一种性能改进方法,它只同步单例类的getInstance()方法中的赋值语句。 一种性能改进的方法 寻找一种性能改进方法时,你可能会选择像下面这样重写getInstance()方法: Java代码 1. publicstaticSingletongetInstance() 2. if(singleton=null) 3. synchronized(Singleton.class) 4. singleton=newSingleton(); 5. 6. 7. returnsingleton; 8. public static Singleton getInstance() if(singleton = null) synchronized(Singleton.class) singleton = new Singleton(); return singleton; 这个代码片段只同步了关键的代码,而不是同步整个方法。然而这段代码却不是线程安全的。考虑一下下面的假定:线程1进入同步块,并且在它给singleton成员变量赋值之前线程1被切换。接着另一个线程进入if块。第二个线程将等待直到第一个线程完成,并且仍然会得到两个不同的单例类实例。有修复这个问题的方法吗?请读下去。 双重加锁检查 初看上去,双重加锁检查似乎是一种使懒汉式实例化为线程安全的技术。下面的代码片段展示了这种技术: Java代码 1. publicstaticSingletongetInstance() 2. if(singleton=null) 3. synchronized(Singleton.class) 4. if(singleton=null) 5. singleton=newSingleton(); 6. 7. 8. 9. returnsingleton; 10. public static Singleton getInstance() if(singleton = null) synchronized(Singleton.class) if
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 大多数考试题目及答案
- 常考焊工考试题目及答案
- 安全生产许可证管理办法讲解
- 推动新质生产力数字化转型
- 新质生产力与金融结合路径
- 各地新质生产力发展新闻盘点
- 新质生产力短视频创作
- 信息技术教学工作方案
- 幼儿园班级毕业典礼活动方案
- 新质生产力的流行与本质
- 2025年新《公司法》知识竞赛题库(附含答案)
- 八年级心理健康体验式教学计划
- 二手房资金监管协议书
- 甘肃省会宁县2025年上半年公开招聘辅警试题含答案分析
- 2025年太阳能海水淡化项目经济效益评估报告
- 2025年机关事业单位工人招聘《机动车驾驶员》技师考试题库与答案
- 2025年物资保管岗位招聘面试实战指南及模拟题解析
- 4D厨房区域区间管理责任卡
- GB/T 990-1991带式输送机托辊基本参数与尺寸
- 猪动物福利及其我国对策课件
- 沟槽坍塌应急演练方案
评论
0/150
提交评论