在设计模式中,单例模式(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里面的两种方式已经可以确保系统中只存在唯一的实例了,但仍然也有例外情况,可能导致系统存在多个实例,比如通过反射机制,调用单例类里面的私有构造函数,就有可能生成多个单例,但是这种情况的特殊性,在此就不做讨论了,否则实现完美单例模式就真的彻底某盖了!
分享到:
相关推荐
通过学习《C++设计模式--基于Qt4开源跨平台开发框架》,开发者不仅可以掌握设计模式的本质和应用场景,还能深入理解Qt4框架的强大功能。结合两者,能够提高代码质量,降低维护成本,同时实现高效且可靠的跨平台应用...
在软件开发中,设计模式是一种在特定情境下解决常见问题的经验总结,被广泛应用于各种编程语言中。在Qt框架中,单例设计模式是一...通过学习和实践,你将能够更好地理解和运用Qt中的单例设计模式,提升你的Qt编程技能。
"设计模式单例模式和工厂模式综合应用"的主题聚焦于两种常用的设计模式:单例模式和工厂模式,并探讨它们如何协同工作来实现高效、灵活的代码结构。这个主题尤其适用于Java编程语言,因为Java的面向对象特性使得设计...
在项目中,`src`目录可能包含了这些设计模式的源码示例,可以用来学习和理解如何实际应用单例模式和工厂模式。通过阅读和分析这些代码,你可以更深入地理解这两种模式的实现细节及其在实际开发中的作用。同时,也...
在C++中,实现单例模式有多种方法,我们将会深入探讨这一模式的原理、优缺点以及如何在实际编程中应用。 单例模式的核心在于控制类的实例化过程,防止多处代码创建多个实例导致资源的浪费或者状态不一致的问题。在...
在学习和使用这些设计模式时,理解其背后的意图和应用场景至关重要。工厂模式用于解耦对象的创建和使用,而单例模式则用于控制对象的唯一性。通过深入理解和实践这些模式,开发者可以编写出更加优雅、易于维护的代码...
通过这个"通俗易懂版"的学习资源,你将能够深入理解每种设计模式的原理,掌握如何在实际项目中应用它们,提升代码的可维护性和复用性。阅读《设计模式_20090916.pdf》文档,结合具体的代码示例,相信你将能够更好地...
在"IOS应用源码Demo-单例模式-毕设学习.zip"这个压缩包中,你可以找到一个关于单例模式的实际示例,这对于正在进行毕业设计或者论文写作的iOS开发者来说是一份宝贵的学习资料。 单例模式的主要特点和优势包括: 1....
学习设计模式可以提升我们的设计思维,帮助我们更好地理解和评估现有设计。通过复用这些模式,我们可以快速解决问题,提高团队之间的沟通效率。 单例模式属于创建型设计模式,还有其他如抽象工厂、工厂方法、建造者...
总之,《新版设计模式手册 - C#设计模式(第二版)》是学习和掌握C#设计模式的宝贵资源,通过学习和实践书中的内容,开发者能够提升软件设计能力,编写出更优雅、可维护的代码。这本书将帮助你从一个代码实现者成长为...
首先,让我们深入了解单例模式。单例模式是一种确保一个类只有一个实例,并提供全局访问点的设计模式。这种模式在资源管理、缓存、对话框、注册表设置、日志记录等场景中非常有用。为了实现单例,通常我们会创建一个...
通过深入学习这23种设计模式及其在C++中的实现,不仅可以提升软件开发的质量,还可以提高个人的技术水平和职业竞争力。这本书不仅适合初学者入门,也非常适合有一定基础的开发者进一步深化理解。
本篇将深入探讨标题中提及的几种设计模式:Model-View-Controller(MVC)模式、单例模式、代理模式以及工厂模式,尤其是简单工厂模式。 **1. Model-View-Controller (MVC) 模式** MVC模式是一种架构模式,它将应用...
首先,让我们深入理解C++设计模式。设计模式分为三大类:创建型模式(如单例、工厂方法、抽象工厂)、结构型模式(如代理、装饰器、适配器)和行为型模式(如观察者、策略、命令)。这些模式都是在特定上下文中解决...
设计模式是软件工程中的一种最佳实践,它是在特定上下文中解决常见问题的模板。这个压缩包文件名为"26种...通过深入学习和实践这些设计模式,开发者可以进一步提升自己的编程技能,更好地应对各种复杂的软件设计挑战。
Java设计模式是面向对象编程中的一种最佳实践,用于解决常见的软件设计问题,提高代码的可重用性、可维护性和可扩展性...因此,深入学习和掌握设计模式不仅能够提升个人的编程技能,也有助于团队协作和项目的成功实施。
《设计模式--基于C#的工程化实现及扩展》是一本深入探讨软件设计模式的书籍,...通过深入学习和实践书中的例子,开发者不仅可以掌握设计模式的基本概念,还能进一步提升自己的编程技能,以应对复杂多变的软件工程挑战。
设计模式包括创建型模式(如单例、工厂方法、抽象工厂等)、结构型模式(如适配器、装饰器、代理等)和行为型模式(如策略、观察者、职责链等)。这些模式提供了良好的可复用性和可扩展性,帮助开发者遵循“开闭原则...
通过研磨设计模式之单例模式的资料,你可以深入理解单例模式的原理、实现方式及其优缺点,进一步提升自己的编程技能和设计思维。学习并熟练掌握设计模式,对于成为一名优秀的Java开发者至关重要。