Koffee设计模式学习之路(二)——单例模式

说起单例模式,应该是最简单的一种设计模式了。从名字上就能看出来,单例模式只涉及到一个类。那么,为什么只希望程序实例化一个类呢?怎样保证只实例化一个类呢?如果实例化乐多个,会有什么影响呢?

一、单例模式的用途

《Head First》上给单例模式的定义是:该模式确保一个类只有一个实例,并提供一个全局访问点。

在实际应用中,很多地方我们只希望有一个类来管理一些东西。比如windows的任务管理器,我们不能打开两个;网站的计数器,应用程序的日志等等。单例模式的使用,保证了资源共享情况下,避免资源操作导致的性能损耗,方便了资源之间的相互通信(需要数据时只要去单例类中获取即可)。

二、单例模式的创建使用思路

从定义中可以看到,创建要保证两个点:1、只有一个实例;2、提供一个全局访问点。

我们知道,C#通过new关键字创建实例。比如:

Demo demoClass = new Demo();

在使用new关键字背后,就调用了Demo类的构造函数。如果该类的构造函数是public,那么意味着该名称空间中别的类都可以实例化这个Demo类,就违背了单例模式的本质。那么,既然要实现单例模式,就要把它的构造函数“藏起来”,即访问权限改为private:

public class Singleton
{
    private static Singleton uniqueInstance; //唯一静态实例
    
    private Singleton() // 私有的构造函数
    {}
    
    //公有全局访问点
    public static Singleton GetInstance()
    {
        if(uniqueInstance == null)
        {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

但是把构造函数都“藏”起来了,别的类还怎么使用它呢?这就涉及到了单例模式的第二个特点:提供一个全局访问点。就是上面代码片段中的GetInstance方法。由于是static的,所以只需要用类名就可以调用该方法,如果Singleton类没有实例,就将它实例化并把这个对象保存在uniqueInstance中;如果已经实例化了,那么就返回这个实例类。因为uniqueInstance也是static的,这就保证了各个地方用到的类对象都是这一个。

至此,一切看起来都很完美;但是实际使用情况要比这个复杂很多。假设有这样的应用场景:有两个线程A和B,要共享一个单例类。在此之前,单例类还没有被使用过,uniqueInstance还是null。A先使用,调用了GetInstance方法;但是当A还没有到实例化uniqueInstance类时,B也调用了GetInstance方法;这时,uniqueInstance还为null,所以B线程也会创建一个Singleton实例。这就违背了单例模式“最初的梦想”。如何解决这个问题?自然就是保证GetInstance方法在同一时间只服务于一个线程。在C#中,该处的实现涉及到线程同步。不多赘述,看改进后的代码:

public class Singleton
{
    private static Singleton uniqueInstance; //唯一静态实例

    private static readonly object locker = new object(); //线程同步的“锁”
    
    private Singleton() // 私有的构造函数
    {}
    
    //公有全局访问点
    public static Singleton GetInstance()
    {
        //线程运行到这里先进行判断,如果locker对象为“加锁”状态,该线程被挂起等待前一个线程结束
        lock(locker)
        {
            if(uniqueInstance == null)
            {        
              uniqueInstance = new Singleton();
            }
        }
        
        return uniqueInstance;
    }
}

用一个lock把new的过程“加锁”,确保GetInstance方法一次只被一个线程使用,解决了上面提到的问题。但是再仔细分析一下这个问题发生的时间点:貌似只有在uniqueInstance为null的情况才有可能new不止一个Singleton类;换言之,只要是uniqueInstance已经被实例化了,那就不用加锁判断了。这时候的锁,反而造成了线程的额外开销。如果可以分清什么时候用锁什么时候不用锁就好了:

public class Singleton
{
    private static Singleton uniqueInstance; //唯一静态实例

    private static readonly object locker = new object(); //线程同步的“锁”
    
    private Singleton() // 私有的构造函数
    {}
    
    //公有全局访问点
    public static Singleton GetInstance()
    {
        //先判断需不需要锁(有没有被实例化)
        if(uniqueInstance == null)
        {
          //线程运行到这里先进行判断,如果locker对象为“加锁”状态,该线程被挂起等待前一个线程结束
            lock(locker)
            {
                if(uniqueInstance == null)
                {        
                     uniqueInstance = new Singleton();
                }
            }
        }  
        return uniqueInstance;
    }
}

在锁外面先判断uniqueInstance是否为null。因为判断了两次,所以也被称为“双重锁定”。这就是单例模式的基本思路。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值