C#设计模式(1)——单例模式

原文地址:http://www.cnblogs.com/zhili/p/SingletonPatterm.html

1、引言

最近在设计模式的一些内容,主要的参考书籍是《Head First 设计模式》,同时在学习过程当中也查看了不少博客园中关于设计模式的一些文章的,在这里记录下个人一些学习笔记,一是为了帮助我更深刻地理解设计模式,二同时能够给一些初学设计模式的朋友一些参考。首先我介绍的是设计模式中比较简单的一个模式——单例模式(由于这里只牵涉到一个类)html

2、单例模式的介绍

说到单例模式,你们第一反应应该就是——什么是单例模式?,从“单例”字面意思上理解为——一个类只有一个实例,因此单例模式也就是保证一个类只有一个实例的一种实现方法罢了(设计模式其实就是帮助咱们解决实际开发过程当中的方法, 该方法是为了下降对象之间的耦合度,然而解决方法有不少种,因此前人就总结了一些经常使用的解决方法为书籍,从而把这本书就称为设计模式),下面给出单例模式的一个官方定义:确保一个类只有一个实例,并提供一个全局访问点。为了帮助你们更好地理解单例模式,你们能够结合下面的类图来进行理解,以及后面也会剖析单例模式的实现思路:设计模式

3、为何会有单例模式

看完单例模式的介绍,天然你们都会有这样一个疑问——为何要有单例模式的?它在什么状况下使用的?从单例模式的定义中咱们能够看出——单例模式的使用天然是当咱们的系统中某个对象只须要一个实例的状况,例如:操做系统中只能有一个任务管理器,操做文件时,同一时间内只容许一个实例对其操做等,既然现实生活中有这样的应用场景,天然在软件设计领域必须有这样的解决方案了(由于软件设计也是现实生活中的抽象),因此也就有了单例模式了。多线程

4、剖析单例模式的实现思路

了解完了一些关于单例模式的基本概念以后,下面就为你们剖析单例模式的实现思路的,由于在我本身学习单例模式的时候,咋一看单例模式的实现代码确实很简单,也很容易看懂,可是我仍是以为它很陌生(这个多是看的少的,或者本身在写代码中也用的少的缘故),并且内心总会这样一个疑问——为何前人会这样去实现单例模式的呢?他们是如何思考的呢?后面通过本身的琢磨也就慢慢理清楚单例模式的实现思路了,而且此时也再也不以为单例模式模式的,下面就分享个人一个剖析过程的:函数

咱们从单例模式的概念(确保一个类只有一个实例,并提供一个访问它的全局访问点)入手,能够把概念进行拆分为两部分:(1)确保一个类只有一个实例;(2)提供一个访问它的全局访问点;下面经过采用两人对话的方式来帮助你们更快掌握分析思路:工具

菜鸟:怎样确保一个类只有一个实例了?性能

老鸟:那就让我帮你分析下,你建立类的实例会想到用什么方式来建立的呢?学习

新手:用new关键字啊,只要new下就建立了该类的一个实例了,以后就可使用该类的一些属性和实例方法了spa

老鸟:那你想过为何可使用new关键字来建立类的实例吗?操作系统

菜鸟:这个还有条件的吗?........., 哦,我想起来了,若是类定义私有的构造函数就不能在外界经过new建立实例了(注:有些初学者就会问,有时候我并无在类中定义构造函数为何也可使用new来建立对象,那是由于编译器在背后作了手脚了,当编译器看到咱们类中没有定义构造函数,此时编译器会帮咱们生成一个公有的无参构造函数)线程

老鸟:不错,回答的很对,这样你的疑惑就获得解答了啊

菜鸟:那我要在哪里建立类的实例了?

老鸟:你傻啊,固然是在类里面建立了(注:这样定义私有构造函数就是上面的一个思考过程的,要建立实例,天然就要有一个变量来保存该实例把,因此就有了私有变量的声明,可是实现中是定义静态私有变量,朋友们有没有想过——这里为何定义为静态的呢?对于这个疑问的解释为:每一个线程都有本身的线程栈,定义为静态主要是为了在多线程确保类有一个实例

菜鸟:哦,如今彻底明白了,可是我还有另外一个疑问——如今类实例建立在类内部,那外界如何得到该的一个实例来使用它了?

老鸟:这个,你能够定义一个公有方法或者属性来把该类的实例公开出去了(注:这样就有了公有方法的定义了,该方法就是提供方法问类的全局访问点)

经过上面的分析,相信你们也就很容易写出单例模式的实现代码了,下面就看看具体的实现代码(看完以后你会惊讶道:真是这样的!):

复制代码
  /// <summary>
    /// 单例模式的实现
    /// </summary>
    public class Singleton
    {
        // 定义一个静态变量来保存类的实例
        private static Singleton uniqueInstance;

        // 定义私有构造函数,使外界不能建立该类实例
        private Singleton()
        {
        }

        /// <summary>
        /// 定义公有方法提供一个全局访问点,同时你也能够定义公有属性来提供全局访问点
        /// </summary>
        /// <returns></returns>
        public static Singleton GetInstance()
        {
            // 若是类的实例不存在则建立,不然直接返回
            if (uniqueInstance == null)
            {
                uniqueInstance = new Singleton();
            }
            return uniqueInstance;
        }
    }
复制代码

 上面的单例模式的实如今单线程下确实是完美的,然而在多线程的状况下会获得多个Singleton实例,由于在两个线程同时运行GetInstance方法时,此时两个线程判断(uniqueInstance ==null)这个条件时都返回真,此时两个线程就都会建立Singleton的实例,这样就违背了咱们单例模式初衷了,既然上面的实现会运行多个线程执行,那咱们对于多线程的解决方案天然就是使GetInstance方法在同一时间只运行一个线程运行就行了,也就是咱们线程同步的问题了(对于线程同步你们也能够参考我线程同步的文章),具体的解决多线程的代码以下:

复制代码
 /// <summary>
    /// 单例模式的实现
    /// </summary>
    public class Singleton
    {
        // 定义一个静态变量来保存类的实例
        private static Singleton uniqueInstance;

        // 定义一个标识确保线程同步
        private static readonly object locker = new object();

        // 定义私有构造函数,使外界不能建立该类实例
        private Singleton()
        {
        }

        /// <summary>
        /// 定义公有方法提供一个全局访问点,同时你也能够定义公有属性来提供全局访问点
        /// </summary>
        /// <returns></returns>
        public static Singleton GetInstance()
        {
            // 当第一个线程运行到这里时,此时会对locker对象 "加锁",
            // 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
            // lock语句运行完以后(即线程运行完以后)会对该对象"解锁"
            lock (locker)
            {
                // 若是类的实例不存在则建立,不然直接返回
                if (uniqueInstance == null)
                {
                    uniqueInstance = new Singleton();
                }
            }

            return uniqueInstance;
        }
    }
复制代码

上面这种解决方案确实能够解决多线程的问题,可是上面代码对于每一个线程都会对线程辅助对象locker加锁以后再判断实例是否存在,对于这个操做彻底没有必要的,由于当第一个线程建立了该类的实例以后,后面的线程此时只须要直接判断(uniqueInstance==null)为假,此时彻底不必对线程辅助对象加锁以后再去判断,因此上面的实现方式增长了额外的开销,损失了性能,为了改进上面实现方式的缺陷,咱们只须要在lock语句前面加一句(uniqueInstance==null)的判断就能够避免锁所增长的额外开销,这种实现方式咱们就叫它 “双重锁定”,下面具体看看实现代码的:

复制代码
  /// <summary>
    /// 单例模式的实现
    /// </summary>
    public class Singleton
    {
        // 定义一个静态变量来保存类的实例
        private static Singleton uniqueInstance;

        // 定义一个标识确保线程同步
        private static readonly object locker = new object();

        // 定义私有构造函数,使外界不能建立该类实例
        private Singleton()
        {
        }

        /// <summary>
        /// 定义公有方法提供一个全局访问点,同时你也能够定义公有属性来提供全局访问点
        /// </summary>
        /// <returns></returns>
        public static Singleton GetInstance()
        {
            // 当第一个线程运行到这里时,此时会对locker对象 "加锁",
            // 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
            // lock语句运行完以后(即线程运行完以后)会对该对象"解锁"
            // 双重锁定只须要一句判断就能够了
            if (uniqueInstance == null)
            {
                lock (locker)
                {
                    // 若是类的实例不存在则建立,不然直接返回
                    if (uniqueInstance == null)
                    {
                        uniqueInstance = new Singleton();
                    }
                }
            }
            return uniqueInstance;
        }
    }
复制代码

5、C#中实现了单例模式的类

 理解完了单例模式以后,菜鸟又接着问了:.NET FrameWork类库中有没有单例模式的实现呢?

通过查看,.NET类库中确实存在单例模式的实现类,不过该类不是公开的,下面就具体看看该类的一个实现的(该类具体存在于System.dll程序集,命名空间为System,你们能够用反射工具Reflector去查看源码的):

复制代码
 // 该类不是一个公开类
    // 可是该类的实现应用了单例模式
    internal sealed class SR
    {
        private static SR loader;
        internal SR()
        {
        }
        // 主要是由于该类不是公有,因此这个所有访问点也定义为私有的了
        // 可是思想仍是用到了单例模式的思想的
        private static SR GetLoader()
        {
            if (loader == null)
            {
                SR sr = new SR();
                Interlocked.CompareExchange<SR>(ref loader, sr, null);
            }
            return loader;
        }

        // 这个公有方法中调用了GetLoader方法的
        public static object GetObject(string name)
        {
            SR loader = GetLoader();
            if (loader == null)
            {
                return null;
            }
            return loader.resources.GetObject(name, Culture);
        }
    }
复制代码

6、总结

到这里,设计模式的单例模式就介绍完了,但愿经过本文章你们能够对单例模式有一个更深的理解,而且但愿以前没接触过单例模式或以为单例模式陌生的朋友看完以后会惊叹:原来如此!

相关文章
相关标签/搜索