`

《漫谈设计模式》

阅读更多
这里给出其中的一章供参考,想阅读书籍全部内容,请参见博客附件
示例代码也在附件里,也可以登录http://code.google.com/p/rambling-on-design-patterns/下载代码。


目前书籍已出版,请查看一下链接:
京东网的链接:《漫谈设计模式》
当当网的链接:《漫谈设计模式》
china-pub链接:《漫谈设计模式》
amazon.cn链接:《漫谈设计模式》


这里特别补充说明一下,希望大家能够把反馈及时发到我的邮箱ramblingondesignpatterns@gmail.com,支持开源共享和原创,谢谢!

申明:关于4.2.3章节的纰漏,请参见博文[置顶] 关于漫谈设计模式4.2.3章节的一点申明,对造成的不便深表歉意。

电子版的勘误请点击:《漫谈设计模式》勘误


                                                                                 第3章             单例(Singleton)模式
3.1           概述
如果要保证系统里一个类最多只能存在一个实例时,我们就需要单例模式。这种情况在我们应用中经常碰到,例如缓存池,数据库连接池,线程池,一些应用服务实例等。在多线程环境中,为了保证实例的唯一性其实并不简单,这章将和读者一起探讨如何实现单例模式。
3.2           最简单的单例
为了限制该类的对象被随意地创建,我们保证该类构造方法是私有的,这样外部类就无法创建该类型的对象了;另外,为了给客户对象提供对此单例对象的使用,我们为它提供一个全局访问点,代码如下所示:
public class Singleton {
    private static Singleton instance = new Singleton();  
    //other fields…

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }
  
    //other methods…
}
代码注解:
l      Singleton类的只有一个构造方法,它是被private修饰的,客户对象无法创建该类实例。
l      我们为此单例实现的全局访问点是public static Singleton getInstance()方法,注意,instance变量是私有的,外界无法访问的。
读者还可以定义instance变量是public的,这样把属性直接暴露给其他对象,就没必要实现public static Singleton getInstance()方法,但是可读性没有方法来的直接,而且把该实例变量的名字直接暴露给客户程序,增加了代码的耦合度,如果改变此变量名称,会引起客户类的改变。

还有一点,如果该实例需要比较复杂的初始化过程时,把这个过程应该写在static{…}代码块中。
l      此实现是线程安全的,当多个线程同时去访问该类的getInstance()方法时,不会初始化多个不同的对象,这是因为,JVM(Java Virtual Machine)在加载此类时,对于static属性的初始化只能由一个线程执行且仅一次[1]
由于此单例提供了静态的公有方法,那么客户使用单例模式的代码也就非常简单了,如下所示:
Singleton singleton = Singleton.getInstance();
3.3           进阶
3.3.1           延迟创建
如果出于性能等的考虑,我们希望延迟实例化单例对象(Static属性在加载类是就会被初始化),只有在第一次使用该类的实例时才去实例化,我们应该怎么办呢?

这个其实并不难做到,我们把单例的实例化过程移至getInstance()方法,而不在加载类时预先创建。当访问此方法时,首先判断该实例是不是已经被实例化过了,如果已被初始化,则直接返回这个对象的引用;否则,创建这个实例并初始化,最后返回这个对象引用。代码片段如下所示:
public class UnThreadSafeSingelton {
    //variables and constructors…

    public static UnThreadSafeSingelton getInstance() {
        if(instatnce ==null){
          instatnce = new UnThreadSafeSingelton();
        }
        return instatnce;
    }
}
我们使用这句if(instatnce ==null) 判断是否实例化完成了。此方法不是线程安全的,接下来我们将会讨论。
3.3.2           线程安全
上节我们创建了可延迟初始化的单例,然而不幸的是,在高并发的环境中,getInstance()方法返回了多个指向不同的该类实例,究竟是什么原因呢?我们针对此方法,给出两个线程并发访问getInstance()方法时的一种情况,如下所示:
    t1                                                                           t2
1   if(instatnce ==null)   
2                                                                      if(instatnce ==null)
3   instatnce = new UnThreadSafeSingelton();   
4   return instatnce;     
5                                                                            instatnce = new UnThreadSafeSingelton()
6                                                                            return instatnce;


如果这两个线程按照上述步骤执行,不难发现,在时刻1和2,由于还没有创建单例对象,Thread1和Thread2都会进入创建单例实例的代码块分别创建实例。在时刻3,Thread1创建了一个实例对象,但是Thread2此时已无法知道,继续创建一个新的实例对象,于是这两个线程持有的实例并非为同一个。更为糟糕的是,在没有自动内存回收机制的语言平台上运行这样的单例模式,例如使用C++编写此模式,因为我们认为创建了一个单例实例,忽略了其他线程所产生的对象,不会手动去回收它们,引起了内存泄露。

为了解决这个问题,我们给此方法添加synchronized关键字,代码如下:
public class ThreadSafeSingelton {
    //variables and constructors…

    public static synchronized ThreadSafeSingelton getInstance() {
        if(instatnce ==null){
            instatnce = new ThreadSafeSingelton();
        }
        return instatnce;
    }
}
这样,再多的线程访问都只会实例化一个单例对象。
3.3.3           Double-Check Locking
上述途径虽然实现了多线程的安全访问,但是在多线程高并发访问的情况下,给此方法加上synchronized关键字会使得性能大不如前。我们仔细分析一下不难发现,使用了synchronized关键字对整个getInstance()方法进行同步是没有必要的:我们只要保证实例化这个对象的那段逻辑被一个线程执行就可以了,而返回引用的那段代码是没有必要同步的。按照这个想法,我们的代码片段大致如下所示:
public class DoubleCheckSingleton {
    private volatile static DoubleCheckSingleton instatnce = null;

    //constructors

    public static DoubleCheckSingleton getInstance() {
        if (instatnce == null) {   //check if it is created.
            synchronized (DoubleCheckSingleton.class) {  //synchronize creation block
                if (instatnce == null)   //double check if it is created
                    instatnce = new DoubleCheckSingleton();
            }
        }
        return instatnce;
    }
}
代码注解:
l      在getInstance()方法里,我们首先判断此实例是否已经被创建了,如果还没有创建,首先使用synchronized同步实例化代码块。在同步代码块里,我们还需要再次检查是否已经创建了此类的实例,这是因为:如果没有第二次检查,这时有两个线程Thread A和Thread B同时进入该方法,它们都检测到instatnce为null,不管哪一个线程先占据同步锁创建实例对象,都不会阻止另外一个线程继续进入实例化代码块重新创建实例对象,这样,同样会生成两个实例对象。所以,我们在同步的代码块里,进行第二次判断判断该对象是否已被创建。

正是由于使用了两次的检查,我们称之为double-checked locking模式。
l      属性instatnce是被volatile修饰的,因为volatile具有synchronized的可见性特点,也就是说线程能够自动发现volatile变量的最新值。这样,如果instatnce实例化成功,其他线程便能立即发现。
注意:
此程序只有在JAVA 5及以上版本才能正常运行,在以前版本不能保证其正常运行。这是由于Java平台的内存模式容许out-of-order writes引起的,假定有两个线程,Thread 1和Thread 2,它们执行以下步骤:
1.     Thread 1发现instatnce没有被实例化,它获得锁并去实例化此对象,JVM容许在没有完全实例化完成时,instance变量就指向此实例,因为这些步骤可以是out-of-order writes的,此时instance==null为false,之前的版本即使用volatile关键字修饰也无效。
2.     在初始化完成之前,Thread 2进入此方法,发现instance已经不为null了,Thread 2便认为该实例初始化完成了,使用这个未完全初始化的实例对象,则很可能引起系统的崩溃。
3.3.4           Initialization on demand holder
要使用线程安全的延迟的单例初始化,我们还有一种方法,称为Initialization on demand holder模式,代码如下所示:
public class LazyLoadedSingleton {
    private LazyLoadedSingleton() {
       }

       private static class LazyHolder {  //holds the singleton class
              private static final LazyLoadedSingleton singletonInstatnce = new LazyLoadedSingleton();
       }

       public static LazyLoadedSingleton getInstance() {
              return LazyHolder.singletonInstatnce;
       }
}
当JVM加载LazyLoadedSingleton类时,由于该类没有static属性,所以加载完成后便即可返回。只有第一次调用getInstance()方法时,JVM才会加载LazyHolder类,由于它包含一个static属性singletonInstatnce,所以会首先初始化这个变量,根据前面的介绍,我们知道此过程并不会出现并发问题(JLS保证),这样即实现了一个既线程安全又支持延迟加载的单例模式。
3.3.5           Singleton的序列化
如果单例类实现了Serializable接口,这时我们得特别注意,因为我们知道在默认情况下,每次反序列化(Desierialization)总会创建一个新的实例对象,这样一个系统会出现多个对象供使用。我们应该怎么办呢?

熟悉Java序列化的读者可能知道,我们需要在readResolve()方法里做文章,此方法在反序列化完成之前被执行,我们在此方法里替换掉反序列化出来的那个新的实例,让其指向内存中的那个单例对象即可,代码实现如下:
import java.io.Serializable;

public class SerialibleSingleton implements Serializable {
    private static final long serialVersionUID = -6099617126325157499L;
    static SerialibleSingleton singleton = new SerialibleSingleton();

    private SerialibleSingleton() {
    }

    // This method is called immediately after an object of this class is deserialized.
    // This method returns the singleton instance.
    private Object readResolve() {
        return singleton;
    }
}
方法readResolve()直接返回singleton单例,这样,我们在内存中始终保持了一个唯一的单例对象。
3.4           总结
通过这一章的学习,我相信大家对于基本的单例模式已经有了一个比较充分的认识。其实我们这章讨论的是在同一个JVM中,如何保证一个类只有一个单例,如果在分布式环境中,我们可能需要考虑如何保证在整个应用(可能分布在不同JVM上)只有一个实例,但这也超出本书范畴,在这里将不再做深入研究,有兴趣的读者可以查阅相关资料深入研究。


________________________________________
[1] Static属性和Static初始化块(Static Initializers)的初始化过程是串行的,这个由JLS(Java Language Specification)保证,参见James Gosling, Bill Joy, Guy Steele and Gilad Bracha编写的《 The Java™ Language Specification Third Edition》一书的12.4一节。
分享到:
评论
49 楼 laobian 2011-10-30  
LZ好,
谢谢你的分享,我对这个序列化不是很了解,看了你的著作的第3章,你说反序列化后直接返回原来内存中的singleton,那反序列化时生成的对象的内容有没有填充到那个singleton中呢?
48 楼 JingAi_jia 2011-10-10  
感谢博主。
47 楼 zcqshine 2011-06-30  
不管书的质量如何,作者的共享精神就得赞一个,下下来学习学习
46 楼 gudgudstudy 2011-05-29  
好书,顶!
45 楼 scofielong 2011-05-25  
支持楼主,希望能有新的作品
44 楼 diwi 2011-05-15  
多谢楼主分享,
43 楼 redhat 2011-05-06  
zqx888191 写道
提点建议 我觉得 你在最后 可以 加上标准的UML图,然后解释下 优缺点和应用的场景。

其实,没有标准的UML,GoF的不是标准,它是一个概括的总结,所以你在后来各个出版的设计模式相关的书籍看到,没有标准。那些模式是OO设计的成熟方案而已,如果你学会了OO,模式根本不重要了,这也是亚历山大在《The timeless way of building》一书中说的,要自然,不要刻意,从问题本质出发,自然而然的选用。
GOF的模式不是适合任何相似的场景,随着你的问题总要变化,关键在于掌握OO的眼光。
42 楼 zqx888191 2011-05-05  
提点建议 我觉得 你在最后 可以 加上标准的UML图,然后解释下 优缺点和应用的场景。
41 楼 redhat 2011-05-05  
yin_bp 写道
redhat 写道
yin_bp 写道
.........

感谢您的关注,我的书籍是讲述常用的设计模式的,虽然在引申讲述IOC的过程中,为了多个角度解释这个概念,我对MVC有点涉猎,那只是非常简单的提到而已。
或许您的软件非常强大,谢谢,我会下来关注,但是此书籍与它应该还是无关的。


我看了下你的所罗列的几种模式:延迟创建 最简单的单例 线程安全   Double-Check Locking ,这个在mvc框架和aop框架都是普遍采用的技术,呵呵



你需要的时候,那模式自然然而的就会被你使用了。
40 楼 yin_bp 2011-05-05  
redhat 写道
yin_bp 写道
.........

感谢您的关注,我的书籍是讲述常用的设计模式的,虽然在引申讲述IOC的过程中,为了多个角度解释这个概念,我对MVC有点涉猎,那只是非常简单的提到而已。
或许您的软件非常强大,谢谢,我会下来关注,但是此书籍与它应该还是无关的。


我看了下你的所罗列的几种模式:延迟创建 最简单的单例 线程安全   Double-Check Locking ,这个在mvc框架和aop框架都是普遍采用的技术,呵呵


39 楼 redhat 2011-05-05  
钢镚儿 写道
楼主抽象工厂你怎么不整呢?也讲讲吧!谢了

抽象工厂比较简单,使用的频率较少,懂得工厂模式,抽象工厂只是看看而已,没必要为学会每个模式而看书,主要是学会OO
38 楼 longhua828 2011-05-05  
看下啊,O(∩_∩)O谢谢
37 楼 钢镚儿 2011-05-05  
楼主抽象工厂你怎么不整呢?也讲讲吧!谢了
36 楼 redhat 2011-05-05  
charyle 写道
“其实我们这章讨论的是在同一个JVM中,如何保证一个类只有一个单例”,说法好像不妥,博主试过同JVM用不同classloader加载单例类没?。。

严格的讲,你的自定义的classloader破坏了你的单例,除非你的jvm是持有不同应用的。如果你要为同一应用设计你的classloader体系,要保证单例,你就得考虑你设计的体系能否保证这样的单例存在,否则,会造成你的单例不是单例。
35 楼 redhat 2011-05-05  
youjianbo_han_87 写道
public class DoubleCheckSingleton {
    private volatile static DoubleCheckSingleton instatnce = null;


此段中的 volatile 关键字是必须的吗?

而volatile关键字就是提示VM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。

  使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,不必使用。

由于使用volatile屏蔽掉了VM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。

在java5以上是必须的,在java4及以下,这个是不能工作的,因为jvm没有保证volatile读写顺序,可以是write out orders的。
volatile 在写的时候是可能产生问题的,但是你要注意,我们这里初始化一次,即写一次,所以volatile不会引起并发的性能问题的。
34 楼 sinopf 2011-05-05  
支持一个,大牛啊!
33 楼 weiertzw 2011-05-05  
顶一个,先收下了,希望中国能多些像博主这样的人
32 楼 oo1238912 2011-05-05  
收下了,非常感谢分享,看完后希望可以有所得
31 楼 jianpc 2011-05-05  
请问楼主的书有出版么?看电子书眼睛疼,还是喜欢坐下来拿本“真实的”书翻一翻。
30 楼 mayEyeInfo 2011-05-05  
谢谢博主,已经下载,感觉此处关于单例模式叙述的很清楚,解决我不少疑惑!希望本书更给力

相关推荐

    漫谈设计模式代码

    《漫谈设计模式》这本书深入浅出地介绍了多种设计模式,通过代码实例帮助读者理解和应用这些模式。在这个压缩包“ramblingonpatterns-1.0”中,你将找到书中的代码示例,它们覆盖了各个章节的关键知识点。 1. **...

    漫谈设计模式

    漫谈设计模式 网络数据 分为模式介绍,创建模式和行为模式几个章节

    漫谈设计模式-从面向对象开始

    在本文档中,我们将深入探讨设计模式及其在面向对象编程中的应用。设计模式是软件工程中的一套被广泛认可的最佳实践,它们是一些在特定上下文中反复出现的问题的解决方案。了解和应用设计模式可以帮助开发者写出更加...

    漫谈设计模式:从面向对象开始(带书签)

    《漫谈设计模式:从面向对象开始(带书签)》设计模式相关书籍。

    漫谈设计模式.rar

    《漫谈设计模式》这本书以其独特的视角深入浅出地探讨了这一主题,尤其针对Java语言进行了详尽的阐述。在这个压缩包中,包含的文件“漫谈设计模式”可能是该书的电子版或者相关的学习资料。 设计模式的核心在于提高...

    漫谈设计模式:从面向对象开始(带书签扫描版).刘济华.pdf

    若想一本书涵盖所有模式,那么这本书将会非常庞大,以前的设计模式书籍专注于介绍设计模式,虽然读者了解了这些设计模式,但是仍然不知道如何合理地使用它们,往往导致读者为了使用设计模式而设计,而不是从问题出发...

    《漫谈设计模式》—Java设计模式的好帮手

    《漫谈设计模式》是一本专为Java开发者深入理解设计模式而编写的书籍。设计模式是软件工程中的一种最佳实践,它们是针对常见问题的解决方案,经过时间和实践的考验,被广泛接受并应用于各种项目中。这本书对于提升...

    漫谈设计模式:从面向对象开始.azw3

    《漫谈设计模式:从面向对象开始》主要从最基本的设计模式入手,并结合一些J2EE开发过程经常遇见的技术和概念,你将全面理解这10多个设计模式,并在开发过程中,让你真正体会和思考面向对象编程的思想,也只有掌握...

    漫谈设计模式:从面向对象开始

    若想一本书涵盖所有模式,那么《漫谈设计模式:从面向对象开始》将会非常庞大,以前的设计模式书籍专注于介绍设计模式,虽然读者了解了这些设计模式,但是仍然不知道如何合理地使用它们,往往导致读者为了使用设计...

    漫谈设计模式-从面向对象开始.刘济华.扫描版

    漫谈设计模式-从面向对象开始.刘济华.扫描版.带详细目录。

    设计模式 pdf

    《漫谈设计模式》这本书是IT领域中关于软件设计的经典之作,主要涵盖了设计模式的核心概念和实际应用。设计模式是经过长期实践证明的在特定情境下解决软件设计问题的通用可复用方案,它们是软件工程中的智慧结晶,...

Global site tag (gtag.js) - Google Analytics