原创 设计模式(一):Singleton模式收藏

新一篇: 设计模式(二):MonoState模式 | 旧一篇:  静态工厂方法

Singleton模式

 

意图:保证类有且仅有一个实例对象,并提供对它的全局访问点。

 

实现

1.  为了实现以上的意图,首先要绕过常规的实例化对象的手法,即通过new直接实例化对象。因为new的方式在“特殊”时候存在两个缺点:第一个很明显,直接new的方式具有较高的耦合性,缺乏相应的灵活度;第二个缺点,类实例化的主动权被客户端程序掌握,而不为类自身所能控制。也就是说,要确保一个类有且仅有一个实例对象,这应该是类设计者的责任,而不应该是类使用者的责任。

2.  Singleton模式的目的是限制类的创建,这是它的重点。所以首先应该将该类的默认构造函数声明为private,以阻止客户端程序使用new直接实例化。并且,声明默认构造函数为private同时也阻止了其他类继承自该类。虽然Singleton能够被继承,但是个人看法是如果Singleton类能够被继承,就有可能产生无数个派生类,这些派生类再实例化对象会导致Singleton模式意图的改变。基于以上的观点,为了突出Singleton类不能够被继承,需要使用明显的关键字来表明(JavafinalC#sealed),同时也有助于提高JIT的效率。

3.  private static的字段表示该类的引用,以及一个对其存取的public static 的方法。该public static的方法就是取得该对象的全局访问点。

 

UML图示 

经典的Singleton实现:(Java

public final class Singleton{
    
private static Singleton instance = new Singleton(); 
    
    
public static Singleton GetInstance(){
        
return instance;
    }

    
private Singleton(){}
}

C#

public sealed class Singleton{
    
private static Singleton instance = new
 Singleton(); 
    
    
public static Singleton Instance
{
        
get

{
return
 instance;
}

    }

    
private Singleton(){}
}

另一种是采用缓式初始化:(Java

public final class Singleton {
    
private static Singleton instance = null
;

    
public static Singleton GetInstance() 
{
// 缓式初始化

        if (instance == null{
            instance 
= new
 Singleton();
        }

        
return instance;
    }


    
private Singleton() {
    }

}

C#

public sealed class Singleton{
    
private static Singleton instance = null
    
    
public static Singleton Instance{
        
get
{
    
// 缓式初始化
    if(instance == null){
instance 
= new Singleton();
}

return instance;
}

    }

    
private Singleton(){}
}

但是缓式初始化的形式if(instance == null)出现在多线程的情况中的时候就可能具有潜在的副作用。如果两个线程同时进入控制块,则可能会创建该成员变量的两个实例。所以为了保证多线程安全,Singleton模式进一步演化为:

线程安全的SingletonJava

public final class Singleton {
    
private static Singleton instance = null;

    
public static synchronized Singleton GetInstance() {
// 缓式初始化
        if (instance == null{
            instance 
= new Singleton();
        }

        
return instance;
    }


    
private Singleton() {
    }

}

Java还有另一种方式实现线程安全的Singleton

public final class Singleton {
    
public final static Singleton instance = new Singleton();

    
private Singleton() {
    }

}

C#

public sealed class Singleton{
    
private static Singleton instance = null
    
static readonly object helper = new object();

    
public static Singleton Instance{
        
get
{
    
lock(helper){
    
// 缓式初始化
    if(instance == null){
instance 
= new Singleton();
}

return instance;
}

}

    }

    
private Singleton(){}
}

从上面的代码看,无论是javasynchronized还是C#lock都对进入if(instance == null)代码块的线程进行了互斥访问控制,所以在同一时刻只有一个线程能够进入if(instance == null)代码块,这样就确保了只有一个实例被创建。但是这种方式有一个缺陷就是无论是javasynchronized还是C#lock都是非常耗费资源的,每次调用GetInstance()方法或者Instance属性都会进行线程同步,这样增加了额外的开销,损失了性能。所以鉴于此,Singleton又演变为Double-checked locking模式。

Double-checked locking模式采用两次检查instance是否被实例化来实现,该模式的有点是克服了上面每次都线程同步消耗大量资源的问题,提高性能。(Java,注:不要使用javaDouble-checked locking模式,因为java的对象模型无法保证Double-checked locking模式的有效性)。

public final class Singleton {
    
private static Singleton instance = null;

    
public static Singleton GetInstance() {
        
//第一次check
        if (instance == null{
synchronized(Class.forName(“Singleton”)){
        
//第二次check
                    if (instance == null{
                             instance 
= new Singleton();
                    }

}

}

        
return instance;
    }


    
private Singleton() {
    }

}

 

C#

public sealed class Singleton{
    
private static volatile Singleton instance = null
    
static readonly object helper = new object();

    
public static Singleton Instance{
        
get
                   
{
                      
// 第一次check
                        if(instance==null){
                                  
lock(helper){
              
// 第二次check
              if(instance == null){
                                       instance 
= new Singleton();
                               }

                       }

                 }

                     
return instance;
             }

    
private Singleton(){}
}

 

在上面的C#代码中,我们除了使用双重检查之外,还添加了volatile关键字,该关键字的含义是保证只有在实例变量分配完成后才能访问实例变量,因为JIT编译器会对代码进行优化,优化后的代码也无法保证对象的唯一性,所以添加volaltile关键字就是明确的告诉编译器不要对此变量进行优化。此种方式解决了线程并发的问题,同时避免了每个Instance属性的调用都出现独占锁定。它还允许将实例化延迟到第一次访问对象时发生。

C#Singleton的优化(静态初始化)

public sealed class Singleton{
    
// 静态初始化
    public static readonly Singleton Instance = new Singleton(); 
    
private Singleton(){}
}

没错!C#的优化实现就这么简单,而且非常直观。此代码相当于:

public