设计模式攻略——单例模式

单例模式分析

  简单说来,单例模式(也叫单件模式)的作用就是保证在整个应用程序的生命周期中,

  任何一个时刻,单例类的实例都只存在一个(当然也可以不存在)。

  需求:

  为什么要有单例模式呢,需求才是最根本的原因。那么究竟是为什么呢。

  场景1.:需要我们做个winform的用户管理信息系统,那么我们需要有注册吧,ok,添加个注册按钮,点击注册按钮是不是应该弹出个注册的界面呢。

假如你做好了,你的boss来检查,好吧,注册一下吧,一点一个注册界面弹出来了,哎,又一点又一个注册界面出来了,哎,再一点又一个界面。你会发现根本停不下来

就像这样,好吧,你的boss马上就会疯了。

  

  显然这种不太太合适,我们应该保证不论何时,点击注册的时候该注册窗体只有一个。

  

  为了保证任何时刻都只有一个,显然不能随意创建,那么创建的过程就要由我们自己控制,且保证只创建一次

1.对外不能创建,那么我们的构造函数,是不是该标记为private

2.为了保证单例,必须有全局唯一的地方存储

3.所有的地方都不能创建实例了,那我们需要提供一个唯一的访问接口。

至于用属性还是用方法,感觉属性更好一点,没有什么太多的理由,访问起来更方便吧。

  

  单例模式1:

  为了保证上面的条件实现如下,

1.私有构造函数,保证了对外不能实例化

2.私有静态字段保证了全局唯一的存储

3.静态属性保证了对外的接口。

    /// <summary>
    /// 问题:不能保证线程安全
    /// </summary>
    public sealed class Singleton1
    {
        private static Singleton1 _instatce1 = null;

        private Singleton1()
        {

        }

        public static Singleton1 Instance1
        {
            get
            {
                if (_instatce1 == null)
                {
                    _instatce1=new Singleton1();
                }
                //_instatce1 = _instatce1 ?? new Singleton1();
                return _instatce1;
            }
        }
    }

  

  这就算ok了么,显然不是,从代码上看,我们并不能保证在多线程访问下,单例的唯一性。

  当两个县城同时访问我们的Instance1属性时,同时判断_instatce1为null就可以同时进入代码,创建对象

  单例模式2:

  直接对静态字段初始化创建实例对象。

  那么用静态字段直接初始化有什么作用呢。

1.在.net中静态构造函数的作用就是初始化类中的静态字段,静态构造函数不需访问修饰符,也不能带任何参数
2.静态构造函数是由.net运行时调用的,而不能在程序中调用,也就无法控制什么时候执行静态构造函数了。
3.由系统保证静态构造函数最多只被调用一次
4.如果没有写静态构造函数,而类中包含带有初始值设定的静态成员,那么编译器会自动生成默认的静态构造函数。
简而言之,我们对静态字段初始化实例,由系统保证只被调用一次,

    /// <summary>
    /// 问题:初始化的时机不能保证,这里系统只保证在元数据中标记BeforeFieldInit,也就是在字段使用前初始化
    /// </summary>
    public sealed class Singleton2
    {

        private static readonly Singleton2 Instance2 = new Singleton2();

        private Singleton2() { }

        public static Singleton2 Instance
        {
            get { return Instance2; }
        }
    }

  我们可以看看IL代码。这个类确实有个 BeforeFieldInit 的标记

  这里我们是没有自定义的静态构造函数的,系统会默认为我们提供一个默认的静态构造

  正如上面分析,该单例方式最大的问题是,静态构造函数是由系统调用,而我们不能保证其调用时机。

  对于beforefieldinit  MSDN是这么解释的。

当一个类型声明显式静态构造函数时,实时 (JIT) 编译器会向该类型的每个静态方法和实例构造函数中添加一项检查,以确保之前已调用该静态构造函数。 访问任何静态成员或创建了类型实例时,都会触发静态初始化。 不过,如果您声明但未使用一个在初始化更改全局状态时非常重要的类型变量,则不会触发静态初始化。

如果已内联初始化所有静态数据,而未声明显式静态构造函数,则 Microsoft 中间语言 (MSIL) 编译器将 beforefieldinit标记和隐式静态构造函数(该构造函数会初始化静态数据)添加到 MSIL 类型定义中。 当 JIT 编译器遇到 beforefieldinit标记时,在大多数情况下,不会添加静态构造函数检查。 静态初始化将保证在访问任何静态字段之前的某个时刻发生,但不会在调用静态方法或实例构造函数前发生。 请注意,声明类型的变量后会随时发生静态初始化。

静态构造函数检查会降低性能。 通常,静态构造函数仅用于初始化静态字段,此时,只需确保静态初始化在第一次访问静态字段之前发生。 beforefieldinit 行为对这些类型和大多数其他类型是适当的。 仅在静态初始化影响全局状态且下列条件之一成立时不适当:

  • 对全局状态的影响将耗费大量资源,如果不使用该类型则无需这样做。
  • 无需访问该类型的任何静态字段,即可访问全局状态影响。

  

  了解了上述信息,我们给你给出又一种单例方式,这种方式既保证了线程安全,又解决了延迟加载的问题。

但是该方式的使用是有条件的,(静态构造函数检查会降低性能。对全局状态的影响将耗费大量资源,如果不使用该类型则无需这样做)

也就是大对象,比较占用资源我们比较适合这么做。

  单例模式4:

    /// <summary>
    /// 静态构造函数方式
    /// 增加了自定义的静态构造函数,那么也就不会在元数据中增加BeforeFieldInit个特性了
    /// 当访问的时候才会调用静态构造函数进行初始化,那么也就是控制了初始化时机(延迟加载)
    /// </summary>
    public class Singleton4
    {
        private static readonly Singleton4 Instance4 = new Singleton4();        

        private Singleton4()
        {
        }

        static Singleton4()
        {
        }

        public static Singleton4 Instance
        {
            get { return Instance4; }
        }

        public static Singleton4 GetInstance()
        {
            return Instance4;
        }
    }

  

  单例模式3:双锁模式

  volatile加双锁模式保证了线程安全的问题。应该也不存在延迟加载的问题。也是种不错的方式。只不过加锁也是会影响一部分效率

    /// <summary>
    /// 双锁模式保证线程安全
    /// Volatile 比同步更简单,只适合于控制对基本变量(整数、布尔变量等)的单个实例的访问。
    /// 当一个变量被声明成 volatile,任何对该变量的写操作都会绕过高速缓存,直接写入主内存,而任何对
    /// 该变量的读取也都绕过高速缓存,直接取自主内存。这表示所有线程在任何时候看到的 volatile 变
    /// 量值都相同。
    /// </summary>
    public sealed class Singleton3
    {
        private static volatile Singleton3 instance3 = null;
        private static readonly object LockObj = new Object();

        private Singleton3() { }

        public static Singleton3 Instance
        {
            get
            {
                if (instance3 == null)
                {
                    lock (LockObj)
                    {
                        if (instance3 == null)
                            instance3 = new Singleton3();
                    }
                }

                return instance3;
            }
        }
    }

  对于volatile

volatile 关键字指示一个字段可以由多个同时执行的线程修改。 声明为 volatile 的字段不受编译器优化(假定由单个线程访问)的限制。 这样可以确保该字段在任何时间呈现的都是最新的值。

volatile总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以死代码消除。但有时这些优化不是程序所需要的,这时可以用volatile关键字禁止做这些优化,volatile的字面含义是易变的,它有下面的作用:  
  
  1 不会在两个操作之间把volatile变量缓存在寄存器中。在多任务、中断、甚至setjmp环境下,变量可能被其他的程序改变,编译器 自己无法知道,volatile就是告诉编译器这种情况。  
  
  2 不做常量合并、常量传播等优化,所以像下面的代码:  
  volatile int i = 1;  
  if (i > 0) ...  
  
  if的条件不会当作无条件真。  
  
  3 对volatile变量的读写不会被优化掉。如果你对一个变量赋值但后面没用到,编译器常常可以省略那个赋值操作,然而对Memory Mapped IO的处理是不能这样优化的。

  volatile多用于多线程的环境,当一个变量定义为volatile时,读取这个变量的值时候每次都是从momery里面读取而不是从cache读。这样做是为了保证读取该变量的信息都是最新的,而无论其他线程如何更新这个变量。

  单例模式5:

  延迟加载机制。

在程序的生存期内,,特别是在这种方式创建或执行可能不发生使用延迟初始化延迟一种或大量占用资源的对象的创建、资源的任务的执行。

若要为延迟初始化准备,您创建 Lazy<T>实例。 Lazy<T> 对象的类型您创建的参数指定要延迟初始化对象的类型。 使用创建 Lazy<T> 对象的构造函数确定初始化的属性。

首次访问 Lazy<T>.Value 属性时会发生惰性初始化。

  正如MSDN建议的一样,一般是占用大量资源的对象采用,估计这种方式也会降低部分效率吧。

    /// <summary>
    /// 初始化 Lazy<T> 类的新实例。 发生延迟初始化时,使用指定的初始化函数,在需要时被调用以产生延迟初始化值的委托。
    /// </summary>
    public class Singleton5
    {
        // 因为构造函数是私有的,所以需要使用lambda
        private static readonly Lazy<Singleton5> Instance5 = new Lazy<Singleton5>(() => new Singleton5());
        //new Lazy<Singleton>(() => new Singleton(), LazyThreadSafetyMode.ExecutionAndPublication);

        private Singleton5()
        {
        }

        public static Singleton5 Instance
        {
            get
            {
                return Instance5.Value;
            }
        }
    }

  开始写的时候一直犹豫是提供方法还是属性呢,方法和属性不是一样的么

纠结半天又查了查,最后直接看IL,才确定没有区别的,不过属性调用更方便些,用属性比较划算,又能省几个括号。

 其实不然,从代码的角度考虑并没有什么不同,但是有些场景却不一定。比如是多线程的情况下,单例需要被lock,那么用方法获取就很容易产生歧义了,

因为给人感觉就是这个对象是我通过方法“获取”到的,所以是独有的;但如果是静态的一个属性,就有一种公有的全局变量的感觉

如果单例是一个“值”,比如Configuration,那么用属性直观点;如果是一个工作对象,比如Worker、Helper、Client,那么用方法获取可能会合适些

  那么net源码中有木有单例模式的应用呢,恩恩,前几天才看了过滤器的源码,怎么能忘了Providers方式提供过滤器呢。

  

    public static class FilterProviders
    {
        // Methods
        static FilterProviders();

        // Properties
        public static FilterProviderCollection Providers { get; private set; }
    }

    static FilterProviders()
    {
        Providers = new FilterProviderCollection();
        Providers.Add(GlobalFilters.Filters);
        Providers.Add(new FilterAttributeFilterProvider());
        Providers.Add(new ControllerInstanceFilterProvider());
    }

  恩恩,通过FilterProviders的静态属性Providers(只读)方式对外提供过滤器,在静态构造函数中FilterProviders进行了初始化。

  这不就是个单例模式么。只不过跟我们的方式稍有不同而已。原来Providers 方式也就是种单例方式而已。

  

  

设计模式攻略——单例模式

时间: 2024-11-05 22:51:15

设计模式攻略——单例模式的相关文章

备战软考(4) 软考下午题攻略

软考的全称是全国计算机技术与软件专业技术资格(水平)考试,而我们今天讨论的是其中的中级职称的一个科目----软件设计师.这个级别的考试主要分为两大块基础知识和应用技术,分别在考试当天的上午和下午进行测试. 对于基础知识这块,因为考查的知识面很广,也很细,个人而言无法找到一个行之有效的办法能让你迅速的提高上午题的成绩,因此就不在这里总结了,我们要做的就是看书,做题,再看书,再做题,然后接着看书,在看书与做题的反复中,一个一个的消灭自己的知识盲点和填补知识漏洞,这样慢慢的也许会有提升,但不要企图短时

程序员技术练级攻略

以下全文来自http://coolshell.cn/articles/4990.html 前言 你是否觉得自己从学校毕业的时候只做过小玩具一样的程序?走入职场后哪怕没有什么经验也可以把以下这些课外练习走一遍(朋友的抱怨:学校课程总是从理论出发,作业项目都看不出有什么实际作用,不如从工作中的需求出发) 建议: 不要乱买书,不要乱追新技术新名词,基础的东西经过很长时间积累而且还会在未来至少10年通用. 回顾一下历史,看看历史上时间线上技术的发展,你才能明白明天会是什么样. 一定要动手,例子不管多么简

3D计算机图形学零起点全攻略(转)

3D计算机图形学零起点全攻略 这篇文章不包含任何技术知识,但我的希望它能指明一条从零开始通往3D领域的成功之路.我将罗列我看过的相关经典书籍作为学习文献,阅读规则是每进入下个内容,我都会假设已经完成前面全部的文献研习内容.相信若能按照这条路走到最后,会有所进益. 完成整部分内容需要具备基础: 英语:CET4以上 数学:精通数字加减乘除法. 物理:基本力学. 计算机:了解电脑的基本知识,熟练使用Windows. 电脑配置: CPU:双核1.5以上 显卡:NVIDIA GeForce8400G MS

转载 程序员技术练级攻略

转载 程序员技术练级攻略 博客分类: 转载 本文转载自陈皓(http://coolshell.cn/articles/author/haoel) 博客: http://coolshell.cn/articles/4990.html 月光博客6月12日发表了<写给新手程序员的一封信>,翻译自<An open letter to those who want to start programming>,我的朋友(他在本站的id是Mailper)告诉我,他希望在酷壳上看到一篇更具操作性的

北美IT求职攻略

http://www.followmedoit.com/bbs/forum.php?mod=viewthread&tid=19&extra=page%3D1 身在北美,想留下来并能过得好,我们就多选了做IT,并且越做越有劲,前仆后继,但机会总是有.外面的人想进来,里面的人习惯了蹦蹦跳跳.成就IT是要努力的,也要不停的发展,所以就有了北美IT之求职攻略1. IT没什么了不起,也不论什么专业,只要你想做有素质付出努力就能做 2. 学习学习再学习+勇气+信念+决心+不放弃+不停的尝试=变成做IT

程序猿技术练级攻略

伯乐人才网6月9日发表了<写给即将入行的程序猿的一封信>,翻译自<An open letter to those who want to start programming>.我的朋友(他在本站的id是Mailper)告诉我,他希望在酷壳上看到一篇更具操作性的文章. 由于他也是喜欢编程和技术的家伙.于是,我让他把他的一些学习Python和Web编程的一些点滴总结一下.于是他给我发来了一些他的心得和经历,我在把他的心得做了不多的增改,并依据我的经历添加了"进阶"一

程序员技术练级攻略(经典)

前言 你是否觉得自己从学校毕业的时候只做过小玩具一样的程序?走入职场后哪怕没有什么经验也可以把以下这些课外练习走一遍(朋友的抱怨:学校课程总是从理论出发,作业项目都看不出有什么实际作用,不如从工作中的需求出发) 建议: 不要乱买书,不要乱追新技术新名词,基础的东西经过很长时间积累而且还会在未来至少10年通用. 回顾一下历史,看看历史上时间线上技术的发展,你才能明白明天会是什么样. 一定要动手,例子不管多么简单,建议至少自己手敲一遍看看是否理解了里头的细枝末节. 一定要学会思考,思考为什么要这样,

云架构师进阶攻略(1)

此文已由作者刘超授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 一.架构的三个维度和六个层面 1.1.三大架构 在互联网时代,要做好一个合格的云架构师,需要熟悉三大架构. 第一个是IT架构,其实就是计算,网络,存储.这是云架构师的基本功,也是最传统的云架构师应该首先掌握的部分,良好设计的IT架构,可以降低CAPEX和OPEX,减轻运维的负担.数据中心,虚拟化,云平台,容器平台都属于IT架构的范畴. 第二个是应用架构,随着应用从传统应用向互联网应用转型,仅仅搞定资源层面的

云架构师进阶攻略(2)

此文已由作者刘超授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 八.基于OpenStack了解云平台 当有了虚拟机,并且虚拟机能够上网了之后,接下来就是搭建云平台的时候了. 云是基于计算,网络,存储虚拟化技术的,云和虚拟化的主要区别在于,管理员的管理模式不同,用户的使用模式也不同. 虚拟化平台没有多层次的丰富的租户管理,没有灵活quota配额的限制,没有灵活的QoS的限制,多采用虚拟网络和物理网络打平的桥接模式,虚拟机直接使用机房网络,没有虚拟子网VPC的概念,虚拟网络