手机怎么免费建网站,wordpress死链接提交,深圳市住建局官网查询,建设网证书查询平台官网概述 单例模式确保一个类只有一个实例#xff0c;而且自行实例化并向整个系统提供整个实例#xff0c;这个类称为单例类#xff0c;它提供全局访问的方法。单例模式是一种对象创建型模式。单例模式有三个要点#xff1a;一是某个类只能有一个实例#xff1b;二是它必须自行… 概述 单例模式确保一个类只有一个实例而且自行实例化并向整个系统提供整个实例这个类称为单例类它提供全局访问的方法。单例模式是一种对象创建型模式。单例模式有三个要点一是某个类只能有一个实例二是它必须自行创建这个实例三是它必须自行向整个系统提供这个实例。 单例模式结构图中只包含一个单例角色
Singleton单例在单例类的内部实现只生成一个实例同时它提供一个静态的getInstance()工厂方法让客户可以访问它的唯一实例为了防止在外部对其实例化将其构造函数设计为私有在单例类内部定义了一个Singleton类型的静态对象作为外部共享的唯一实例。 一个简单的单例模式如下 以上单例无法保证线程安全当多个线程同时调用时由于实例尚未创建成功仍为null因此new LogManager()会被执行多次最终导致创建多个实例对象这违背了单例模式的初衷也导致系统运行发生错误。为了解决该问题有以下几种方式
实现方式
饿汉式单例类 当类被加载时静态变量logManager就会被初始化单例类的唯一实例将被创建。无论多个线程调用都保证了实例的唯一性确保线程安全。
懒汉式单例类
懒汉式单例在第一次调用getInstance()方法时实例化在类加载时并不自行实例化这种技术又称为延迟加载(Lazy Load)技术即需要的时候再加载实例为了避免多个线程同时调用getInstance()方法我们可以使用关键字synchronized代码如下所示
问题貌似得以解决事实并非如此。如果使用以上代码来实现单例还是会存在单例对象不唯一。原因如下 假如在某一瞬间线程A和线程B都在调用getInstance()方法此时instance对象为null值均能通过instance null的判断。由于实现了synchronized加锁机制线程A进入synchronized锁定的代码中执行实例创建代码线程B处于排队等待状态必须等待线程A执行完毕后才可以进入synchronized锁定代码。但当A执行完毕时线程B并不知道实例已经创建将继续创建新的实例导致产生多个单例对象违背单例模式的设计思想因此需要进行进一步改进在synchronized中再进行一次(instance null)判断这种方式称为双重检查锁定(Double-Check Locking)。使用双重检查锁定实现的懒汉式单例类完整代码如下所示 需要注意的是如果使用双重检查锁定来实现懒汉式单例类需要在静态成员变量instance之前增加修饰符volatile被volatile修饰的成员变量可以确保多个线程都能够正确处理且该代码只能在JDK 1.5及以上版本中才能正确执行。由于volatile关键字会屏蔽Java虚拟机所做的一些代码优化可能会导致系统运行效率降低因此即使使用双重检查锁定来实现单例模式也不是一种完美的实现方式。
饿汉式单例类与懒汉式单例类比较
饿汉式单例类在类被加载时就将自己实例化它的优点在于无须考虑多线程访问问题可以确保实例的唯一性从调用速度和反应时间角度来讲由于单例对象一开始就得以创建因此要优于懒汉式单例。但是无论系统在运行时是否需要使用该单例对象由于在类加载时该对象就需要创建因此从资源利用效率角度来讲饿汉式单例不及懒汉式单例而且在系统加载时由于需要创建饿汉式单例对象加载时间可能会比较长。 懒汉式单例类在第一次使用时创建无须一直占用系统资源实现了延迟加载但是必须处理好多个线程同时访问的问题特别是当单例类作为资源控制器在实例化时必然涉及资源初始化而资源初始化很有可能耗费大量时间这意味着出现多线程同时首次引用此类的机率变得较大需要通过双重检查锁定等机制进行控制这将导致系统性能受到一定影响。 有没有一种方法能够将两种单例的缺点都克服而将两者的优点合二为一呢答案是Yes下面我们来学习这种更好的被称之为Initialization Demand Holder (IoDH)的技术。在IoDH中我们在单例类中增加一个静态(static)内部类在该内部类中创建单例对象再将该单例对象通过getInstance()方法返回给外部使用实现代码如下所示
由于静态单例对象没有作为LogManager的成员变量直接实例化因此类加载时不会实例化LogManager第一次调用getInstance()时将加载内部类HolderClass在该内部类中定义了一个static类型的变量instance此时会首先初始化这个成员变量由Java虚拟机来保证其线程安全性确保该成员变量只能初始化一次。由于getInstance()方法没有任何线程锁定因此其性能不会造成任何影响。 通过使用IoDH我们既可以实现延迟加载又可以保证线程安全不影响系统性能不失为一种最好的Java语言单例模式实现方式其缺点是与编程语言本身的特性相关很多面向对象语言不支持IoDH。
单例模式被破坏
克隆、反射和反序列化会对传统的单例模式破坏。
在其他创建型设计模式的学习中我们已经了解除了直接通过new和使用工厂来创建对象以外还可以通过克隆、反射和反序列化等方式来创建对象。但是用这些方式来创建对象时有可能会导致单例对象的不唯一如何解决这些问题呢 (1) 为了防止客户端使用克隆方法来创建对象单例类不能实现Cloneable接口即不能支持clone()方法。 (2) 由于反射可以获取到类的构造函数包括私有构造函数因此反射可以生成新的对象。【如何解决采用枚举实现】 采用一些传统的实现方法都不能避免客户端通过反射来创建新对象此时我们可以通过枚举单例对象的方式来解决该问题。 (3) 在原型模式中我们可以通过反序列化实现深克隆反序列化也会生成新的对象。具体来说就是每调用一次readObject()方法都将会返回一个新建的实例对象这个新建的实例对象不同于类在初始化时创建的实例对象。 那么如何防止反序列化创建对象呢解决方法一是类不能实现Serializable接口即不允许该类支持序列化这将导致类的应用受限制有时候我们还是需要对一个对象进行持久化处理解决方法二就是本文将要详细介绍的枚举实现。
简单实现 下面我们分析如何使用枚举Enum来实现单例模式。 Google 首席 Java 架构师、《Effective Java》一书作者、Java集合框架的开创者Joshua Bloch在Effective Java一书中提到 单元素的枚举类型已经成为实现Singleton的最佳方法。在这种实现方式中既可以避免多线程同步问题还可以防止通过反射和反序列化来重新创建新的对象。在很多优秀的开源代码中我们经常可以看到使用枚举方式来实现的单例类。 下面我们来详细分析如何使用枚举实现单例模式。 枚举是在JDK1.5以及以后版本中增加的一个“语法糖”它主要用于维护一些实例对象固定的类。例如一年有四个季节就可以将季节定义为一个枚举类型然后在其中定义春、夏、秋、冬四个季节的枚举类型的实例对象。 按照Java语言的命名规范通常枚举的实例对象全部采用大写字母定义这一点与Java里面的常量是相同的。 首先我们来看一下最简单的单例模式枚举实现。 因为Java虚拟机会保证枚举对象的唯一性因此每一个枚举类型和定义的枚举变量在JVM中都是唯一的。 最简单的实现方式如下代码所示 在main()函数中我们通过Singleton.INSTANCE获得两个对象s1和s2然后比较s1是否等于s2最后输出true说明s1和s2是同一个对象所得到的对象具有唯一性。由于单例模式的枚举实现代码比较简单而且又可以利用枚举的特性来解决线程安全和单一实例的问题还可以防止反射和反序列化对单例的破坏因此在很多书和文章中都强烈推荐将该方法作为单例模式的最佳实现方法。
单例模式总结
单例模式作为一种目标明确、结构简单、理解容易的设计模式在软件开发中使用频率相当高在很多应用软件和框架中都得以广泛应用。
主要优点 单例模式的主要优点如下 (1) 单例模式提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例所以它可以严格控制客户怎样以及何时访问它。 (2) 由于在系统内存中只存在一个对象因此可以节约系统资源对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。 (3) 允许可变数目的实例。基于单例模式我们可以进行扩展使用与单例控制相似的方法来获得指定个数的对象实例既节省系统资源又解决了单例单例对象共享过多有损性能的问题。
主要缺点 单例模式的主要缺点如下 (1) 由于单例模式中没有抽象层因此单例类的扩展有很大的困难。 (2) 单例类的职责过重在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色提供了工厂方法同时又充当了产品角色包含一些业务方法将产品的创建和产品的本身的功能融合到一起。 (3) 现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术因此如果实例化的共享对象长时间不被利用系统会认为它是垃圾会自动销毁并回收资源下次利用时又将重新实例化这将导致共享的单例对象状态的丢失。
适用场景 在以下情况下可以考虑使用单例模式 (1) 系统只需要一个实例对象如系统要求提供一个唯一的序列号生成器或资源管理器或者需要考虑资源消耗太大而只允许创建一个对象。如ServletContextServletConfigApplicationContextDBPoolThreadPool等。 (2) 客户调用类的单个实例只允许使用一个公共访问点除了该公共访问点不能通过其他途径访问该实例。