概述
此系列文章为清华大学出版社出版刘伟编著《Java设计模式》的学习笔记。
1 概述
有时候确保系统中某个对象的唯一性非常重要,比如一个系统只能有一个窗口管理器或文件系统,一个系统只能有一个计时工具或 ID(序号)生成器等。
如何保证一个类只有一个实例并且这个实例易于被访问呢?解决办法就是让类自身负责创建和保存它的唯一实例,并保证不能创建其他实例,然后提供一个公共的访问点向外界提供这一个唯一的实例。
这就是单例模式的动机和思想。
单例模式:确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。
2 结构与实现
2.1 单例模式结构
单例模式式结构最简单的设计模式,它只包含了一个类,即单例类。
单例类 (Singleton):在单例类的内部创建它的唯一实例,并通过静态方法 getInstance() 让客户端可以使用它的唯一实例;为了防止在外部对单例类实例化,将其构造器函数的可见性设为 private ;在单例类内部定义了一个 Singleton 类型的静态对象作为供外部共享访问的唯一实例。
2.2 单例模式实现
接下来我们用三种方式,来实现单例模式。
饿汉式
懒汉式
IoDH
无论什么方式,单例类都有以下特征:
单例类内部创建静态的私有的实例
私有化构造器
提供公共的静态的 getInstance() 方法并在这个方法中返回唯一实例
2.2.1 饿汉式 Eager_Singleton
public class Eager_Singleton {
private static final Eager_Singleton instance = new Eager_Singleton();
private Eager_Singleton(){}
public static Eager_Singleton getInstance(){
return instance;
}
}
复制代码
当单例类被加载时,静态变量 instance 会被初始化:此时类的私有构造函数会被调用,单例类的唯一实例将被创建。
2.2.2 懒汉式 Lazy_Singleton
一、仅 synchronized 修饰
public class Lazy_Singleton_1 {
private static Lazy_Singleton_1 instance = null;
private Lazy_Singleton_1(){}
public static synchronized Lazy_Singleton_1 getInstance(){
if (instance == null){
instance = new Lazy_Singleton_1();
}
return instance;
}
}
复制代码
在上述懒汉式单例类中,在 getInstance() 方法前面增加了关键字 synchronized 进行线程锁定,以处理多个线程同时访问的问题。上述代码虽然解决了线程安全问题,但是每次调用 getInstance() 方法时都需要进行线程锁定判断,在多线程高并发访问环境中会导致系统性能大大降低。因此可以继续对懒汉式单例进行改进,通过分析不难发现无需对整个 getInstance() 方法进行锁定,只需对其中的实例创建代码进行锁定即可。
二、单层锁
public class Lazy_Singleton_2 {
private static Lazy_Singleton_2 instance = null;
private Lazy_Singleton_2(){}
public static Lazy_Singleton_2 getInstance(){
if (instance == null){
synchronized(Lazy_Singleton_2.class){
instance = new Lazy_Singleton_2();
}
}
return instance;
}
}
复制代码
使用以上代码来实现单例类,还是会存在单例对象不唯一的情况,原因是:
加入在某一个瞬间线程 A 和线程 B 都在调用 getInstance() 方法,此时 instance 对象为 null 值,均能通过“instance == null”的判断。由于实现了 synchronized 加锁机制,线程 A 进入 synchronized 锁定的代码中执行实例创建代码,线程 B 处于排队等待状态,必须等待线程 A 执行完毕后才可以进入 synchronized 锁定的代码。但当 A 执行完毕时线程 B 并不知道实例已经创建,将继续新的实例,导致产生多个实例对象,违背了单例模式的设计思想,因此需要进一步改进,在 synchronized 中再进行一次“instance == null”判断,这种方式称为双重检查锁定(Double-Check Locking)。使用双重检查锁定实现的懒汉式单例类的完整代码如下。
三、双重检查锁定(DCL)
public class Lazy_Singleton_DCL {
private static volatile Lazy_Singleton_DCL instance = null;
private Lazy_Singleton_DCL(){}
public static Lazy_Singleton_DCL getInstance(){
if (instance == null){
synchronized(Lazy_Singleton_DCL.class){
if (instance == null){
instance = new Lazy_Singleton_DCL();
}
}
}
return instance;
}
}
复制代码
需要注意的是,如果使用双重检查锁定来实现懒汉式单例类,需要在静态成员变量 instance 之前增加修饰符 volatile ,被 volatile 修饰的成员变量可以确保多个线程都能够正确处理,且该代码只能在 JDK 1.5 及以上版本中才能正确执行。由于 volatile 关键字会屏蔽 Java 虚拟机所做的一些代码优化(指令重排),可能会导致系统的运行效率降低,因此即使使用双重检查锁定来实现单例模式也不是一种完美的实现方式。
2.2.3 IoDH
饿汉式单例类不能实现延迟加载,不管将来用不用始终占据内存;懒汉式单例类线程安全控制繁琐,而且性能受影响。可见,无论是饿汉式单例还是懒汉式单例都存在一些问题。为了克服这些问题,在 Java 语言中可以通过 Initialization on Demand Holder (IoDH) 技术来实现单例模式。
在 IoDH 中,需要在单例类中增加一个静态(static)内部类,在该内部类中创建单例对象,再将该单例对象通过 getInstance() 方法返回给外部使用:
public class IoDH_Singleton {
private IoDH_Singleton(){}
private static class InstanceHolder{
private static final IoDH_Singleton instance = new IoDH_Singleton();
}
public static IoDH_Singleton getInstance(){
return InstanceHolder.instance;
}
}
复制代码
由于静态单例对象没有作为单例类的成员变量直接实例化,因此类加载时不会创建 instance 实例。第一次调用 getInstance() 方法时,将加载静态内部类 InstanceHolder ,在该内部类中定义了一个 static 类型的变量 instance ,此时会首先初始化这个成员变量,由 Java 虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于 getInstance() 方法没有任何线程锁定,因此其性能不会造成任何影响。
通过 IoDH 既可以实现延迟加载,又可以保证线程安全,不影响系统性能,不失为一种最好的 Java 语言单例模式实现方式;其缺点是与编程语言本身的特性相关,很多面向对象语言并不支持 IoDH 。
2.2.3 饿汉式、懒汉式与 IoDH 对比
饿汉式
DCL 懒汉式
IoDH
优点
无需考虑多线程同时访问的问题,可以确保实例的唯一性;从调用速度和反应时间来讲,由于单例对象一开始就得以创建,因此要优于懒汉式。
支持延迟加载,在单例对象需要时才去创建,因此懒汉式单例类加载时间更短,资源利用灵活性更高。
支持延迟加载,又可以保证线程安全,不影响系统性能,是 Java 语言实现单例模式的最佳方式。
缺点
单例对象在饿汉式单例类加载时即被创建,饿汉式单例类加载时间更长,资源利用灵活性更低。
懒汉式单例类线程安全控制繁琐,而且性能受影响;单例对象的调用速度和反应时间更长。
与编程语言特性相关,很多面向对象语言并不支持 IoDH 。
3 总结
3.1 单例模式优点
单例模式提供了对唯一实例的受控访问。因此单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它。
由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统性能。
允许可变数目的实例。基于单例模式可以进行扩展,使用与控制单例对象相似的方法来获得指定个数的实例对象,既节省系统资源,又解决了由于单例对象共享过多有损性能的问题。(注:自行提供指定数目实例对象的类可称为多例类。)
3.2 单例模式缺点
由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
单例类的职责过重,在一定程度上违背了单一职责原则。因为单例类既提供了业务方法,又提供了创建对象的方法(工厂方法),将对象的创建和对象本身的功能耦合在一起。
现在很多面向对象语言(如 Java ,C# 等)的运行环境都提供了自动垃圾回收技术,因此如果实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的实例对象状态的丢失。
3.3 单例模式适用环境
系统只需要一个实例对象,例如系统要求提供一个唯一的序列号生成器或资源管理器,或者因为资源消耗太大而只允许创建一个对象。
客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
最后
以上就是开心百合为你收集整理的局部变量初始化java创建型设计模式,Java设计模式-创建型模式-单例模式的全部内容,希望文章能够帮你解决局部变量初始化java创建型设计模式,Java设计模式-创建型模式-单例模式所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复