单例设计模式的研究与实现
2017-10-12葛萌欧阳宏基陈伟咸阳师范学院计算机学院陕西咸阳712000
葛萌, 欧阳宏基, 陈伟(咸阳师范学院计算机学院, 陕西 咸阳 712000)
单例设计模式的研究与实现
葛萌, 欧阳宏基, 陈伟
(咸阳师范学院计算机学院, 陕西 咸阳 712000)
单例设计模式能够保证某个类的对象在内存中只有唯一的实例,该实例易于被外界访问并节约系统资源。分析了单例模式的角色构成和创建要点,分别从饿汉式、懒汉式、双重验证式、类加载式、枚举式等5个方面阐述了单例模式的实现过程,总结了5种形式的优缺点和应用场景。将单例模式应用到JDBC数据库开发中,设计了一个JDBC工具类,为单例模式的实际应用提供了一定的参考。
单例模式; 设计模式; JDBC; 类加载; 枚举
Abstract: The singleton design pattern ensures that one class has only one object instance in memory, the instance is easy to be accessed by the outside world and save system resources. The role composition and the key points of the singleton pattern are analyzed,the implementation process of the singleton pattern is described from five aspects of hungry type, lazy type, double authentication type, class loading type and enumeration type. Then it summarizes the advantages and disadvantages of those five types, and their application scenarios. The singleton pattern is applied to the database development with JDBC, and a JDBC tool class is designed. It provides a reference for the practical application of the singleton pattern.
Keywords: singleton pattern; design pattern; JDBC; class loading; enumeration
0 引言
在利用面向对象技术进行应用系统开发中合理使用设计模式,能够获得灵活的应用架构、良好的扩展性和较高的复用率。设计模式是一组精心设计的类和接口的组合,包含若干角色,每个类或接口充当一种角色承担一个职责。设计模式通常在某个应用场景下使用,它
为某种重复出现的问题提供一种最佳的指导方针和解决方案。GOF等人总结了软件开发中使用频率最高的23种设计模式,并根据模式的目的将它们分为三类:创建型、结构型和行为型。创建型模式主要用于创建对象;结构型模式用于处理类或对象的组合;行为型模式主要用于描述类或对象如何交互并分配责任。每个模式都包含四个关键的要素:模式名称、问题、解决方案和效果[1]。
单例设计模式是使用最为频繁的一种创建型模式,它的主要思想是:确保某个类在内存中只有一个对象,这个对象的创建由所属类自身负责,无论其它任何对象如何调用,都只能操作这个唯一的对象。因此,合理的使用单例模式能够将减少对象的创建次数并区分对象创建的责任,从而减少内存的消耗,提高复用性。本文首先介绍了单例设计模式的角色构成与实现要点,然后从饿汉式、懒汉式、双重验证式、类加载式、枚举式等5个方面分析了单例模式的实现并给出了示例代码,从线程安全、延迟加载、调用效率等几个方面对5种实现方式进行了对比分析。最后,将单例模式应用到JDBC数据库操作中,设计了一个JDBC工具类。
1 单例设计模式分析
单例设计模式只包含一个角色,即单例类,如图1所示[2]。
图1 单例模式结构图
从类的定义角度考虑,创建单例类必须满足三个要素:首先,在其内部包含一个自身类型的静态成员,作为外部共享的唯一实例;其次,只有唯一的构造方法且访问权限为私有,避免外部对象通过new关键字对其实例化。最后,提供一个公有的静态getInstance()工厂方法,用来获取静态的自身成员,这个工厂方法作为访问这个唯一对象的全局入口。
2 单例模式的实现方式
2.1 饿汉式
如示例代码1所示:
示例代码1
public class SingletonClass {
private static final SingletonClass instance=new SingletonClass();
private SingletonClass (){}
public static SingletonClass getInstance(){
return instance;}}
从示例代码1可以看出,该类中有一个静态成员instance,该成员在类加载时就被创建生成实例对象,final关键字确保该对象不被自身所修改。唯一的构造方法是私有访问权限,确保了外部类无法创建SingletonClass的对象;同时该构造方法对子类不可见,也就保证不能通过继承的方式来创建SingletonClass的对象。获取唯一实例的方式是调用静态getInstance()方法。由于这个唯一的对象是在类加载的时候创建,因此称为饿汉式。由于类加载器是线程安全的,所以生成的单例对象也是线程安全的,但此方式不能做到对单例对象的延迟加载。延迟加载是节省内存的一种有效手段,强调在使用某个对象的时候再创建该对象,避免提前创建而长时间又不使用该对象的情况[3-4]。
2.2 懒汉式
如示例代码2所示:
示例代码2
public class SingletonClass{
private static SingletonClass instance=null;
private SingletonClass (){}
public static synchronized SingletonClass getInstance(){
if(instance==null){
instance=new SingletonClass();
}
return instance;
}}
从示例代码2可以看出,在类加载时并不创建单例对象,而是在调用getInstance()方法时判断单例对象是否存在,如果不存在再创建并返回该对象的引用。由于将单例对象的创建延迟到使用时才创建,所以称为懒汉式。考虑到多线程环境下的线程同步问题,在静态方法getInstance()前加入synchronized 关键字,确保多线程环境下单例对象只创建一次。懒汉式做到了延迟加载,但每次在调用getInstance()方法时都需要线程同步,调用效率较低。
2.3 双重验证式
如示例代码3所示:
示例代码3
public class SingletonClass{
private static SingletonClass instance=null;
private SingletonClass(){}
public static SingletonClass getInstance(){
//第一次判断单例对象是否已经存在
if(instance==null){
synchronized(SingletonClass.class)
{
//第二次判断单例对象是否已经存在
if(instance==null){
instance=new SingletonClass();
} }
return instance;
}}
双重验证方式中,将获取单例对象方法的同步机制移到了方法体内,避免每次调用getInstance()方法就执行同步。首先判断单例对象是否已经被创建,如果没有创建,申请对单例类的Class对象加锁,在同步块内再次判断单例对象是否已经被创建,如果没有才创建单例对象。在同步块内对单例对象是否存在进行第二次判断,可以避免多个线程都进入到第一次判断为true的分支中,从而造成单例对象的多次创建。双重验证方式实现了单例对象的延迟加载,在getInstance()方法调用时只有在创建单例对象时需要同步,一旦创建后,就不再进行同步,提高了调用效率。
2.4 静态内部类式
如示例代码4所示:
示例代码4
public class SingletonClass{
private SingletonClass(){}
private static class SingletonClassHolder{
private static final SingletonClass instance=new SingletonClass();
}
public static SingletonClass getInstance(){
return SingletonClassHolder.instance;} }
在SingletonClass中定义了一个私有的静态内部类SingletonClassHolder,其中的instance就是外部类的单例对象。当外部类第一次调用getInstance()方法时才加载内部类并创建instance对象,实现了延迟加载。由于instance是静态常量,所以只创建一次并且不能被修改。以后调用getInstance()方法不会再创建instance对象,确保是单例对象。类加载器是在线程同步的环境下运行的,能够保证instance对象的创建也是线程同步的[5]。
2.5 枚举式
如示例代码5所示。
示例代码5:
public enum SingletonClass{
//定义一个枚举元素,表示单例对象
INSTANCE;
//定义枚举的其他方法
public void operation(){
……………..} }
枚举是JDK1.5引入的新特性,由Java虚拟机保证枚举本身就是单例模式,也是线程安全的。枚举元素INSTANCE就是这个枚举类型的唯一对象,其中可以添加其他的方法供INSTANCE调用。枚举方式实现单例具有实现简单、代码量最少、线程安全等特点,但无法保证延迟加载[6]。
2.6 屏蔽反序列化与克隆
在Java中创建对象的方式有四种,分别是通过new创建对象、反射机制、反序列化和克隆。饿汉式、懒汉式、双重验证式和静态内部类方式都屏蔽了通过new和反射形式创建单例模式对象,但在序列化和克隆要求的特殊情况下,无法做到屏蔽通过反序列化和克隆方式来创建单例模式对象。假定单例模式对象需要实现序列化和克隆功能,示例代码1、示例代码2、示例代码3和示例代码4必须增加readResolve()和覆盖clone()方法,以示例代码2为例进行修改,如示例代码6所示。
示例代码6
public class SingletonClass implements Serializable{
private static SingletonClass instance=null;
private SingletonClass(){}
public static synchronized SingletonClass getInstance(){
if(instance==null){
instance=new SingletonClass();
}
return instance;
}
//自定义反序列化返回的对象
private Object readResolve() throws ObjectStreamException{
return getInstance();
}
//执行克隆方法时返回的对象
protected Object clone() throws CloneNotSupportedException{
return getInstance();
}}
在readResolve()和clone()方法中调用getInstance()返回唯一的单例对象,这样就避免了在单例模式实现序列化和克隆功能的情况下是一个真正的单例对象。枚举类型在反序列化和克隆操作时生成的对象还是自己,所以无需考虑序列化和克隆问题。
2.7 单例模式各实现方式对比
从表1可以看出,单例模式的五种实现方式都是线程是安全的。调用效率方面,懒汉式和双重验证式都要进行同步,所以要低于饿汉式、静态内部类和枚举方式;延迟加载方面,懒汉式、双重验证式和静态内部类式在调用getInstance()方法时才创建单例对象,支持延迟加载;饿汉式和枚举式都是在字节码加载时创建单例对象,不支持延迟加载。 在序列化和克隆情景下,枚举式无需添加额外逻辑来保证单例的纯粹性;而其余四种方式需要添加readResolve()方法和重写clone()方法来保证反序列化和克隆操作所得到的结果仍然是当前单例对象。
表1 单例模式各实现方式比较
3 单例模式在JDBC中的应用
JDBC是Java语言用来访问关系型数据库的一组API,具有使用成本低、执行速度快、不依赖于业务容器等优点,通常在Java EE分层模型中作为持久层的解决方案[7]。JDBC操作过程一般包括4个步骤:①加载数据库驱动;②创建数据库连接对象Connection;③通过创建Statement对象执行SQL语句;④释放资源。持久层通常采用DAO模式向上层调用者屏蔽访问数据库的细节,在DAO接口中定义实体类的持久化方法,在DAO实现类封装JDBC操作数据库。假如DAO实现类中的每个持久化操作都按照上述步骤编码,就会存在驱动类重复加载、代码冗余度高、复用率低等缺点。由于操作①②④与实体对象以及持久化逻辑没有太多联系,所以可通过单例模式封装成一个工具类,如图2所示,确保数据库驱动只加载一次,同时作为数据库访问的唯一入口。在实体对象的DAO实现中引用这个工具类来执行数据库操作,DAO中只关注步骤③的操作即可,从而降低代码的冗余度并提高复用率。
4 结论
单例设计模式保证单例类只能生成一个实例对象,对于系统中管理资源的类、通过线程同步控制并发操作的类而言用单例模式封装能够保证对资源的合理控制和引用。如果单例对象占用资源少、实现简单、不考虑序列化和克隆场景并且不需要延迟加载时,则优先使用枚举式。如果单例对象占用资源大、需要延迟加载并且在序列化和克隆场景中使用,则优先使用静态内部类式。如果在多线程环境中,尽量不要使用懒汉式和双重验证式。
图2 JDBC数据库访问工具类
[1] 陈烽,陈蓉,王跟成,孙懿.设计模式在区域综合管网中的应用研究[J].计算机技术与发展,2015,25(4):193-196.
[2] 刘伟.设计模式[M].北京:清华大学出版社,2011:92-103.
[3] 欧阳宏基,葛萌,赵蔷.基于JDBC与设计模式的数据库连接池实现方法[J].计算机技术与发展,2011,21(1):84-87.
[4] 夏浩波.单例模式的设计与应用[J].电脑开发与应用,2011,24(1):58-59.
[5] 张磊杰,刘永久,王 慧.基于虚拟现实的步态训练康复机器人系统软件设计[J].计算机系统应用,2012,21(12):8-11.
[6] 陈天超.单例设计模式研究[J].福建电脑,2016,(8):14-16.
[7] 欧阳宏基,葛萌,陈伟.基于JDBC的数据持久化层性能优化研究[J].网络新媒体技术,2016,5(5):9-15.
ResearchandImplementationofSingletonDesignPattern
Ge Meng, Ouyang Hongji, Chen Wei
(Computer College, Xianyang Normal University, XianYang 712000, China)
A
)
葛萌(1980-),女,讲师,硕士,研究方向为软件工程. 欧阳宏基(1982-),男,讲师,硕士,研究方向为Java EE应用,软件工程. 陈伟(1976-),男,讲师,硕士,研究方向为网络安全.
1007-757X(2017)09-0068-03