设计模式之单例模式

来源:互联网 发布:stl文件编辑软件 编辑:程序博客网 时间:2024/06/11 07:11
目录
导读
(一)什么是单例模式
(二)如何保证单例
(三)单例模式的应用场景
(四)单例模式类图
(五)单例模式的几种写法

导读
如果你想把单例模式比作什么的话,我觉得比作老婆就很好,老婆只能有一个,多了容易出问题,多了容易消耗很多资源,而且还会产生冲突矛盾,所以老婆有一个就很好了。但是想要保证只有一个老婆,
可得花费不小力气,哈哈,不扯淡了,正经说单例模式吧。
(一)什么是单例模式
单例模式(Singleton Pattern),顾名思义,就是被单例的对象只能有一个实例存在。单例模式的实现方式是,一个类能返回对象的一个引用(永远是同一个)和一个获得该唯一实例的方法(必须是静态方法)。通过单例模式,我们可以保证系统中只有一个实例,从而在某些特定的场合下达到节约或者控制系统资源的目的。
以上是官方定义,看到这里肯定像我一样,不知所云,因为我在看单例模式的时候直接看了它几种写法,心中全是问号。从字面理解,就是只有单一的实例的设计模式,就是单例模式了,那么什么类我们只需要实例化一次,只允许有一个对象存在呢。其实很多,比方说Windows下的任务管理器,处理偏好设置和注册表的对象,还有比如充当打印机、显卡等设备的驱动程序的对象,多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。这样的类我们只希望它存在一个实例,否则的话,会出现各种各样的问题。
(二)如何保证单例。
既然说是单例模式,那么如何做到保证这个类只能被实例化一次呢。说到这里,我们先想一下如果偏要保证只有一个实例,那么就设定一个全局变量不就可以了吗?当然不是得,确实我们可以进行这种约定,在程序员之间约定就可以办到,但是啊,如果我们把一个实例化得对象赋值给了一个全局变量,那么必须在程序得一开始就创建好对象对吧,这样假如在一次程序执行当中我们没有用到这个对象,并且该对象又非常的消耗资源,那就非常不妥,不过马上就会发现在单例模式中,只有当使用这个单例的时候才会创建它。

回到如何保证实例化一次的问题上,总不会是所有的程序员都时刻想着某个实例只能有一次,永远不创建该实例吧。当然不是,想一下我们要创建一个实例首先第一步是要执行下列语句:

new MyObject();
只要MyObject是公开类,那么我就可以用其它的对象来创建它,私有类呢,虽然无法在其它类中创建新的该类的实例,但是在这个私有类当中仍旧是可以创建多个实例的。但是加入这个类是这样的:

public Class MyClass(){private MyClass();...}
这个类的构造方法定义成私有化的了,那么除了在这个类里面可以创建它的实例外,在其他任何地方都不可以了,那么这就失去了意义,没有MyClass的实例可以调用MyClass构造器,而MyClass构造器是私有构造方法,没有实例可以调用它,太混乱了,完蛋了,晕了。其实没什么奇怪的,就是如此,现有鸡还是先有蛋,总是让人迷惑的。
那么再看下面的代码:

public Class MyClass(){public static MyClass getInstance(){}...}
这里定义了一个静态方法,也就类方法,对于类方法的访问,直接就是“类.方法名”,也就是

MyClass.getInstance();
看到这里是不是终于可以找到创建单例的方法了,那就通过调用类方法来创建实例。通过以上分析,我们来写一段单例模式(代码2.1):
代码2.1
public class Singeton{private static Singleton uniqueInstance;private  Singeton(){}public static Singleton getInstance(){if(uniqueInstance == null){uniqueInstance = new Singeton();}return uniqueInstance;}}
上面这段代码就保证了创建单例,先别急着拿去用这样的写法可能并不完美。从代码中看出,只要我们每次需要用到这个单例的时候,就可以直接调用getInstace()这个类方法了。

(三)单例模式的应用场景
在第一张已经说过一些应用的场景了,这里做个扩展,举一些列子,利于我们理解,也方便我们开发工作中发现类似的场景,考虑是否使用单例模式。
1. Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 
2. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
3. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
4. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
5. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
6. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
7. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
8. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
9. HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一HttpApplication实例.
总结以上,不难看出:
  单例模式应用的场景一般发现在以下条件下:
  (1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
  (2)控制资源的情况下,方便资源之间的互相通信。如线程池等。
(四)单例模式类图


(五)单例模式的几种写法
上面说过,单例模式(代码2.1)的写法有一些问题,究竟有哪些问题呢,我们还能怎么优化呢,先来看看当两个线程同时执行2.1这段代码时,在JVM中发生了什么,现在我们假设有两个线程都在执行

Singleton testObject = Singeton.getInstance();

奇怪的事情出现了,说好的单例,居然出现了两个实例,这就说明我们之前“代码2.1”的设计是不够合理的,无法保证多线程操作。那么有什么好的解决方法呢,只需要把getInstance()方法变成同步(synchronized)方法。

代码5.1

public class Singeton{private static Singleton uniqueInstance;private  Singeton(){}public static synchronized Singleton getInstance(){if(uniqueInstance == null){uniqueInstance = new Singeton();}return uniqueInstance;}}

添加了关键字synchronized后,就迫使每个线程进入这个方法之前,要等候其他线程离开,这样就不会有两个线程同时进入这个方法了。


这样确实解决了多线程造成了问题,但是,同步是非常耗费资源的。因为会出现多线程操作造成多个实例被创建的情况只有首次执行方法时才会出现。但是同步设置只要进行getInstance()
方法就要同步,实际上只要第一次创建了uniqueInstance变量后就不再需要同步这个方法了,对于之后的调用,同步反而是一种累赘。
那么解决的方法有什么呢?这里我们提供一些方法。
1、如果应用程序可以接受同步所带来的负担,那么就保持代码5.1的样子;
2、使用一种急切的方式,如果总创建并使用单例,并且创建和运行时方面的负担不大,那么可以这样做:

代码5.2

public class Singleton{private static Singleton uniqueInstance = new Singleton();private Singleton();public static Singleton getInstance(){return uniqueInstance;}}
这样做就相当以来JVM在加载该类时马上创建唯一的单例。JVM保证了在任何线程访问uniqueInstance静态变量之前,都已经创建好这个实例了。
3使用双重检查加锁,只有第一次使用getInstance()方法才进行同步。
代码5.3
public class Singeton{private volatile static Singleton uniqueInstance;private  Singeton(){}public static synchronized Singleton getInstance(){if(uniqueInstance == null){synchronized (Singleton.class){if(uniqueInstance == null){uniqueInstance = new Singeton();}}}return uniqueInstance;}}

注意这里加入了volatile关键字,它确保了当uniqueInstance变量被初始化成Singleton实例时,多个线程正确地处理uniqueInstance变量。不过voatile在旧版本中不支持,即使支持,但是修修补补,总是感觉代码不够精简,其实还有两种方法,一种是静态内部类(代码5.4),一种是枚举(代码5.5)。前者利用了JVM自身的机制保证先行安全,类似于代码2.1;后者枚举法,代码清新脱俗,而且它是线性安全的,可以防止反序列化的问题。
下面我们就总结一下
1、饿汉模式,对应代码5.2

public class Singleton{private static Singleton uniqueInstance = new Singleton();private Singleton(){}public static Singleton getInstance(){return uniqueInstance;}}
2、懒汉模式,对应代码2.1

public class Singeton{private static Singleton uniqueInstance;private  Singeton(){}public static Singleton getInstance(){if(uniqueInstance == null){uniqueInstance = new Singeton();}return uniqueInstance;}}
3、线性安全的懒汉模式,对应代码5.1

public class Singeton{private static Singleton uniqueInstance;private  Singeton(){}public static synchronized Singleton getInstance(){if(uniqueInstance == null){uniqueInstance = new Singeton();}return uniqueInstance;}}
4、双重检查加锁,对应代码5.3

public class Singeton{private volatile static Singleton uniqueInstance;private  Singeton(){}public static synchronized Singleton getInstance(){if(uniqueInstance == null){synchronized (Singleton.class){if(uniqueInstance == null){uniqueInstance = new Singeton();}}}return uniqueInstance;}}
5、静态内部类
代码5.4
public class Singleton{private static class SingletonHolder{private static final Singleton uniqueInstance = new Singleton();}private Singleton(){}public static Singleton getInstance(){return SingletonHolder.uniqueInstance;}}
6、枚举
代码5.5
public enum Singleton{UNIQUEINSTANCE;}
枚举法对单例的访问,只要Singleton.UNIQUEINSTANCE;就可以了。