大话设计模式之单例模式
版权声明:本文为博主原创文章,转载需注明出处。 http://blog.****.net/qq_29517037
相信大家对于单例都不陌生,我们主要总结一下单例模式的演化过程,其实学设计模式不是说一定让你遇到什么情况去套哪种设计模式,更多的是要学习里面的程序设计思想,让我们在遇到复杂问题的时候遵循设计原则,培养的是我们设计高质量代码的能力。我们对于高质量代码的定义,我认为就是:可维护,可复用,可扩展,灵活性好。
好了言归正传总结一下单例设计模式的演化过程。情景大概就是:我们需要某个对象实例有且仅有一次。所以我们如何能够判断类什么时候实例呢?我们要把声明的工作放到类的全局变量中。代码一示例如下:
public class Singleton{
private static Singleton instance;
public static Singleton GetInstance(){
if(instance == null){
instance = new Singleton():
}
return instance;
}
}
通过判断instance是否为空来判断对象是否实例过了。但是现在新的问题就来了,一个类是否实例过应该由这个类自己负责,但是每个类都有默认的构造方法,如果不对构造方法做改动的话,是不可能阻止他人不去new对象的。当我们显示定义了构造方法,默认构造方法就会失效。所以我们将构造方法私有,保证他们无法通过new获得该对象实例,这样一来,单例类是否实例过了什么时候实例就完全由它自己负责了,代码二示例如下:
public class Singleton{
private static Singleton instance;
private Singleton(){}
public static Singleton GetInstance(){
if(instance == null){
instance = new Singleton():
}
return instance;
}
}
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。单例模式UML图如下:
单例模式因为Singleton类封装它的唯一实例,这样它可以严格控制客户怎样访问它以及何时访问它。简单地说就是对唯一实例的受控访问。
单例模式的演化还没有结束,在多线程时,多个线程同时调用GetInstance()方法,会有可能造成创建多个实例的。基于此我们对代码进行第三次演化,代码三示例如下:
public class Singleton{
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton():
}
return instance;
}
}
加了锁之后,当一个线程进入到GetInstance方法的时候,其他线程会被阻塞,会影响性能。所以我们对代码三进行第四次演化,代码四示例如下:
public class Singleton {
private volatile static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance== null) { //先判断实例是否存在,不存在再加锁处理
synchronized (Singleton.class) {
if (instance== null) {
instance= new Singleton();
}
}
}
return singleton;
}
}
我们不让线程每次都加锁,而只是在实例未被创建的时候再加锁处理。同时也能保证多线程的安全。这种做法被称为Double-CheckLocking(双重锁定)。相信不少童鞋看到这里都会有问题,我们已经判断了instance实例是否存在,为什么在同步锁里还要再判断一次呢?
当instance存在的情况,就直接返回没什么问题。但是当instance实例不存在的时候,并且同时有两个线程调用GetInstance()方法时,它们将都可以通过第一重instance==null的判断,然后由于lock机制,这两个线程只有一个进入,另一个在外排队等候,此时如果没有第二重instance==null的判断的话,第一个线程创建实例后,第二个线程还是可以继续再创建新的实例,这就没有达到单例的目的。
以上便是单例演化的全过程,但是单例模式还不止如此。我们看一下代码四,我们先利用全局变量声明了实例却没有实例化,而是在静态方法GetInstance调用的时候才对Singleton进行了实例,这种延迟实例的模式我们称之为懒汉式。相对的就有恶汉式的代码实例:
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
描述:这种方式比较常用,但容易产生垃圾对象。优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
恶汉式是在声明全局变量的时候便将对象实例化,虽然相比之前双重锁定的懒汉式代码更加简洁但是同样有很多的问题,基于恶汉式单例的代码我们也有近一步的演化:
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
我们称其为登记式(静态内部类),这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloder 机制来保证初始化 instance 时只有一个线程,它跟恶汉式不同的是:恶汉式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比恶汉式就显得很合理。
经验之谈:一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用登记方式。如果有其他特殊的需求,可以考虑使用双重锁定的懒汉方式。