`
swolf119
  • 浏览: 6429 次
文章分类
社区版块
存档分类
最新评论

深入学习设计模式之---单例模式

阅读更多
在设计模式中,单例模式(singleton)算是应用最普遍的一种设计模式。
顾名思义,单例就是获取对象唯一的实例,它是一种对象创建模式,用于产生一个对象的具体实例,它可以确保系统中一个类只产生一个实例。在Java语言中,这种行为能带来两大好处:

1、对于频繁使用的对象,可以省略创建对象所花费的时间和消耗,这对于那些重量级对象而言,是非常可观的一笔开销。
2、由于new操作的次数减少,因为对系统内存的使用频率也会降低,这样会减轻GC压力,缩短GC停顿时间。
因此对于系统的关键组件和被频繁使用的对象,如系统中频繁使用的字典值存储等,使用单例模式可以有效的改善系统的性能。



1、简单的实现:
public Class Singleton{
    private Singleton(){
    }
    private static Singleton instance = new Singleton();
    private static Singleton getInstance(){
        return instance;
    }
}

如上例,外部使用者如果需要使用SingletonClass的实例,只能通过getInstance()方法,并且它的构造方法是private的,这样就保证了只能有一个对象存在


2、延迟加载:
上述这个单例实现非常简单,也非常的可靠,它唯一的不足就是无法对类进行延迟加载,例如单例的创建过程很慢,而instance成员变量又是static的,在JVM加载单例类的时候就会被创建,如果这时,这个单例类还在系统中扮演着其他角色,那么在任何使用该单例类中的方法的地方都会初始化这个单例变量,而不管是否被用到。那么怎么办呢?
可以看一下下面的方案:

public Class LazySingleton{
    private LazySingleton(){
    }
    private static LazySingleton instance = null;
    private static synchronized LazySingleton getInstance(){
        if(instance == null ){
            instance = new LazySingleton();
        }
        return instance;
    }
}

首先对于静态成员变量instance初始值赋予null,确保系统启动的时候没有额外的负荷。其次在调用getInstance()方法时,会先判断当前单例是否已经存在,若不存在,再创建单例,避免了重复创建,但在多线程中必须要用synchronized关键字修饰,避免线程A进入创建过程还未创建完毕之前线程B也通过了非空判断导致重复创建。


3、同步性能
上述代码看起来已经完美的实现了单例,有了同步锁,一个线程必须等待另外一个线程创建完成后才能使用这个方法,这就保证了单例的唯一性。但是大家都知道,被synchronized修饰的同步块要比一般代码慢上很多倍,如果存在很多次getInstance()的调用,那性能问题就不得不考虑了!那么为了引入延迟加载而使用同步关键字反而降低了系统性能,是否有些得不偿失呢?

让我们来分析一下,究竟是整个方法都必须加锁,还是仅仅其中某一句加锁就足够了?我们为什么要加锁呢?分析一下出现延迟加载的那种情形的原因。原因就是检测null的操作和创建对象的操作分离了。如果这两个操作能够原子地进行,那么单例就已经保证了。于是,我们开始修改代码:

public Class LazySingleton{
    private LazySingleton(){
    }
    private static LazySingleton instance = null;
    private static LazySingleton getInstance(){
        synchronized (LazySingleton.class) { 
            if(instance == null ){
                instance = new LazySingleton();
            }
        }
        return instance;
    }
}

首先去掉getInstance()的同步操作,然后把同步锁加载if语句上。但是这样的修改起不到任何作用:因为每次调用getInstance()的时候必然要同步,性能问题还是存在。如果……如果我们事先判断一下是不是为null再去同步呢?
public Class LazySingleton{
    private LazySingleton(){
    }
    private static LazySingleton instance = null;
    private static LazySingleton getInstance(){
        if(instance == null){
            synchronized (LazySingleton.class) { //语句1
                if(instance == null ){    //语句2
                    instance = new LazySingleton();//语句3
                }
            }
        }
        return instance;
    }
}

还有问题吗?首先判断instance是不是为null,如果为null,加锁初始化;如果不为null,直接返回instance。
到此为止,一切都很完美。我们用一种很取巧的方式实现了单例模式。



4、Double-Checked Locking
这其实是Double_Checked Locking设计实现的单例模式
到目前为止,看似已经完美的解决了问题,性能和实现共存,但真的是这样吗?
让我们来分析一下:
让我们直接看语句3,JIT产生的汇编代码所做的事情并不是直接生成一个LazySingleton对象,然后将地址赋予instance,相反的,它的执行顺序如下:
1.先申请一块内存
2.将这块内存地址赋予instance
3.在instance所指的地址上构建对象

试想一下:如果线程调度发生在 instance 已经被赋予一个内存地址,而 Singleton 的构造函数还没有被调用的微妙时刻,那么另一个进入此函数的线程会发觉 instance 已经不为 null ,从而放心大胆的将 instance 返回并使用之。但是这个可怜的线程并不知道此时 instance 还没有被初始化呢!
由于Java的memory model允许out-of-order write,现在的症结就在于:首先,构造一个对象不是原子操作,而是可以被打断的;第二,更重要的,Java 允许在初始化之前就把对象的地址写回,这就是所谓 out-of-order 。


5、实现方案
扯了这么多,怎么就没有个完美的实现方案?
在JDK 5之后,Java使用了新的内存模型。volatile关键字有了明确的语义——在JDK1.5之前,volatile是个关键字,但是并没有明确的规定其用途——被volatile修饰的写变量不能和之前的读写代码调整,读变量不能和之后的读写代码调整!因此,只要我们简单的把instance加上volatile关键字就可以了

public Class LazySingleton{
    private LazySingleton(){
    }
    private volatile static LazySingleton instance = null;
    private static LazySingleton getInstance(){
        if(instance == null){
            synchronized (LazySingleton.class) { //语句1
                if(instance == null ){    //语句2
                    instance = new LazySingleton();//语句3
                }
            }
        }
        return instance;
    }
}

volatile关键字保证了内存可见性,最新的值可以立即被所有线程可见

其实还有另外一种实现方案,就是采取内部类

public class Singleton{
    private static class SingletonHolder{
        private static final Singleton instance = new Singleton();
    }
    public static SingletonHolder getInstance(){
        return SingletonHolder.instance;
    }
 
    private Singleton(){       
    }
}


在这个实现中,单例模式使用了内部类来维护单例的实例,当Singleton被加载时,内部类不会被初始化,可以确保当Singleton被加载到虚拟机时,不会初始化单例类,而当getInstance()方法被调用时,才会加载Singleton,从而初始化instance,并且,JSL规范定义,类的构造必须是原子性的,非并发的,因此不需要加同步块。同样,由于这个构造是并发的,所以getInstance()也并不需要加同步。

通常情况下,5里面的两种方式已经可以确保系统中只存在唯一的实例了,但仍然也有例外情况,可能导致系统存在多个实例,比如通过反射机制,调用单例类里面的私有构造函数,就有可能生成多个单例,但是这种情况的特殊性,在此就不做讨论了,否则实现完美单例模式就真的彻底某盖了!

分享到:
评论

相关推荐

    最新设计模式超级详解+Tomcat架构源码分析+Spring源码分析 资深级设计模型课程

    Spring源码分析,web源码分析,Tomcat架构源码分析都是非常深入的源码级课程,期待研究设计模式和深入学习源码内功的朋友们,一定要仔细的学习研究。 (0);目录中文件数:1个 ├─3.代码.zip (1)\1.笔记;目录中文...

    C#设计模式–单例模式

     近在学设计模式的一些内容,主要的参考书籍是《Head First 设计模式》,同时在学习过程中也查看了很多博客园中关于设计模式的一些文章的,在这里记录下我的一些学习笔记,一是为了帮助我更深入地理解设计模式,二...

    Java设计模式教程

    该资料介绍Java各类开发模式,包含以下教程:《深入浅出设计模式(中文版)》《Java单例模式》《Java设计模式-图解-附代码》《JAVA设计模式之单例模式(完整版)》《Java学习笔记(必看经典)》《Java总复习》《单例模式》...

    深入浅出java设计模式(高清中文PDF)

    文件类型为PDF文件,此文档对20多种java设计模式进行了详细讲解,在中文讲解的过程中还附有代码示例给学习者进行参考,使学习者通过实践更容易理解设计模式的原理。 本文档目录: 1.工厂模式 2.单例模式 3.建造...

    研磨设计模式-part2

    也可以作为高效学生深入学习设计模式的参考读物! 第1章 设计模式基础 第2章 简单工厂 第3章 外观模式 第4章 适配器模式(Adapter) 第5章 单例模式(Singleton) 第6章 工厂方法模式(Factory Method) 第7章...

    研磨设计模式-part4

    也可以作为高效学生深入学习设计模式的参考读物! 第1章 设计模式基础 第2章 简单工厂 第3章 外观模式 第4章 适配器模式(Adapter) 第5章 单例模式(Singleton) 第6章 工厂方法模式(Factory Method) 第7章...

    研磨设计模式-part3

    也可以作为高效学生深入学习设计模式的参考读物! 第1章 设计模式基础 第2章 简单工厂 第3章 外观模式 第4章 适配器模式(Adapter) 第5章 单例模式(Singleton) 第6章 工厂方法模式(Factory Method) 第7章...

    研磨设计模式(完整带书签).part2.pdf

    也可以作为高效学生深入学习设计模式的参考读物! 第1章 设计模式基础 第2章 简单工厂 第3章 外观模式 第4章 适配器模式(Adapter) 第5章 单例模式(Singleton) 第6章 工厂方法模式(Factory Method) 第7章...

    【Java设计模式】你对单例模式了解多少,一文深入探究

    目录单例模式懒汉式单例模式未初始化问题解决Double Check 双重检查方案一:不让第二步和第三步重排序-DoubleCheck方案二:基于类初始化-静态内部类饿汉式饿汉式与懒汉式最大区别序列化破坏单例模式原理枚举单例基于...

    研磨设计模式(完整带书签).part1.pdf

    也可以作为高效学生深入学习设计模式的参考读物! 第1章 设计模式基础 第2章 简单工厂 第3章 外观模式 第4章 适配器模式(Adapter) 第5章 单例模式(Singleton) 第6章 工厂方法模式(Factory Method) 第7章...

    深入浅出设计模式(中文版)

    3.6SingletonPattern(单例模式) 82 3.6.1定义 82 3.6.2现?抵械牡ダ??猈indowsTaskManager 83 3.6.3C#实例——负载均衡控制器 84 3.6.4Java实例——系统日志 86 3.6.5DoubleCheckLocking(双检锁) 89 3.6.6...

    Android源码设计模式解析与实战

    本书专门介绍Android源代码的设计模式,共26章,主要讲解面向对象的六大原则、主流的设计模式以及MVC和MVP模式。主要内容为:优化代码的首步、开闭原则、里氏替换原则、依赖倒置原则、接口隔离原则、迪米特原则、...

    深入浅出设计模式(中文版电子版)

    3.6SingletonPattern(单例模式) 82 3.6.1定义 82 3.6.2现?抵械牡ダ??猈indowsTaskManager 83 3.6.3C#实例——负载均衡控制器 84 3.6.4Java实例——系统日志 86 3.6.5DoubleCheckLocking(双检锁) 89 3.6.6...

    java设计优化之单例模式

    主要为大家详细介绍了java设计优化中的单例模式,深入学习java单例模式,感兴趣的朋友可以参考一下

    design-pattern-java.pdf

    工厂三兄弟之抽象工厂模式(二) 工厂三兄弟之抽象工厂模式(三) 工厂三兄弟之抽象工厂模式(四) 工厂三兄弟之抽象工厂模式(五) 单例模式-Singleton Pattern 确保对象的唯一性——单例模式 (一) 确保对象的...

    Android 源码设计模式解析与实战

    本书专门介绍Android源代码的设计模式,主要讲解面向对象的六大原则、主流的设计模式以及MVC和MVP模式。本书的主要内容为:优化代码的第一步、开闭原则、里氏替换原则、依赖倒置原则、接口隔离原则、迪米特原则、...

    Android源码设计模式解析与实战PDF

    本书专门介绍Android源代码的设计模式,共26章,主要讲解面向对象的六大原则、主流的设计模式以及MVC和MVP模式。主要内容为:优化代码的首步、开闭原则、里氏替换原则、依赖倒置原则、接口隔离原则、迪米特原则、...

    《Android 源码设计模式解析与实战》

    本书专门介绍Android源代码的设计模式,共26章,主要讲解面向对象的六大原则、主流的设计模式以及MVC和MVP模式。主要内容为:优化代码的首步、开闭原则、里氏替换原则、依赖倒置原则、接口隔离原则、迪米特原则、...

    [Java设计模式(第2版)(Design.Patterns.in.Java).John.Metsker

    《java设计模式(第2版)》通过一个完整的java项目对经典著作design patterns一书介绍的23种设计模式进行了深入分析与讲解,实践性强,却又不失对模式本质的探讨。本书创造性地将这些模式分为5大类别,以充分展现各个...

Global site tag (gtag.js) - Google Analytics