我眼中的单例模式并不完美

image
/// <summary>
    /// 全局唯一的配置信息
    /// </summary>
   public class Config
    {
        private static Config _config = null;
        public static Config GetConfig()
        {
            if (_config == null)
            {
                _config = new Config();
            }
            return _config;
        }
    }
image

单例模式

所谓单例,就是整个程序有且仅有一个实例。该类负责创建自己的对象,同时确保只有一个对象被创建。

几乎大部分程序员面试的时候,面试官让你说出三种常用的设计模式,单例必是其中之一。平时所说的单例模式是指一个进程内只存在某个类型的一个实例,其实扩展到集群这个概念,位于不同物理环境的多个进程之间也可以有单例这种概念,像平时吹水用的分布式锁其实可以看做是多进程之间的单例模式。

透过单例的现象可以看到单例模式本质上也是解决资源竞争的问题,它让多个线程甚至多个进程共享同一个资源,以达到资源共享的目的。为什么要实现资源共享呢?因为这个资源在业务场景下只能存在一个实例,例如以上的全局配置信息,如果程序内部有多个配置信息实例,不仅浪费了服务器的内存资源,在配置信息发生修改的时候,多个实例随之同步更新又是一个很大的问题。

单例实现

单例模式的概念很简单,实现的方式也有很多,但是关注点却无外乎以下几个:

  1. private的构造函数,主要是为了避免外部通过New创建实例
  2. 单例是否支持延迟加载,以及性能是否高效
  3. 多线程环境下,对象创建是否安全
  4. 全局只有一个访问入口

为了达到以上几个要求,我们可以有很多的实现方法

饿汉式
public class Config
    {
        private Config() { }
        private static Config _config = new Config ();
        public static Config GetConfig()
        {
           
            return _config;
        }
    }

这种方式主要是利用了语言特性,一个类型的静态属性是属于类型所有,在类的生命周期内只会加载一次,所以以上代码实现单例模式并没有问题,而且超级简单。

很多人说这种方式不妥,在类型的加载时候就完成实例创建,没有达到惰性加载,会造成内存的浪费。至于这个问题我并不表示完全赞同。如果一个单例的初始化耗时比较长,最好不要等到真正用它的时候才去执行初始化,这样会影响系统的性能。饿汉式可以实现在程序启动的时候就进行初始化操作,这样就能避免初始化时间过长导致的性能问题,而且还有一个比较重要的好处,如果初始化程序有错误,我们可以在程序启动的时候就发现,而不用等到程序上线运行时才暴露出来。这就好比编译期错误永远比运行时错误好排查的道理类似。

懒汉式

程序员妹子贡献的代码其实就属于懒汉式,表面上看可以实现惰性加载,但是在多线程的环境下,会产生多个实例,问题就在于 if (_config == null) 这个语句并非是线程安全的。如果非要改造的话,可以加上全局的锁机制,有一个注意点,这里锁的对象一定要是一个static全局的对象

 private static object objLock = new object();
        private static Config _config = null;
        public static Config GetConfig()
        {
            lock (objLock)
            {
                if (_config == null)
                {
                    _config = new Config();
                }
            }           
            return _config;
        }
双重加锁机制

虽然懒汉式方式能保证线程安全,但是锁的机制缺大大降低了系统性能,原因是锁机制把所有请求顺序化了,为了改善懒汉式的性能,所以双重加锁机制出现了,在保证了线程安全的情况下,大大提高了程序性能

private static object objLock = new object();
        private static Config _config = null;
        public static Config GetConfig()
        {
            if (_config == null)
            {
                lock (objLock)
                {
                    if (_config == null)
                    {
                        _config = new Config();
                    }
                }
            }
                
            return _config;
        }

以上只是实现单例的几种常用方式,根据每个语言的特性还有很多可以实现单例的方式,比如:利用c#或者java的内部类,静态初始化特性等都可以实现线程安全的单例模式。

image

单例模式缺陷

  1. 面向对象设计讲究封装,继承,多态,以及抽象。单例模式对于其中的继承,多态支持得不好,抽象讲究的是面向接口编程,单例模式并没有接口概念。拿以上配置文件的单例为例,假设现在的配置信息是以本地文件的方式进行加载,如果后期要加入从数据库加载配置信息这个需求,单例模式必须修改现有代码,这在一定程度上就违反了设计规则。所以单例在一定程度上丢失了应对未来需求的扩张性。
  2. 单例模式在职责上有时候会过重,即要负责初始化的过程,又要负责初始化的内容,甚至在某些情况下还要负责其他程序,这在一定程度上违反了“单一职责”原则。
  3. 由于单例模式对外之后一个入口点,并没有显示的利用构造函数传参的方式进行初始化,内部使用了哪些类型并不能很快识别出来,开发人员很难识别出类的依赖关系
  4. 单例模式并不适合那些表面是单例,但是未来还有可能扩展的场景。举个栗子:线程池在很多程序中都被设计成单例模式,很多开发人员认为程序中只存在一个线程池,但是在个别需求下,同一个程序需要多个线程池的场景是存在的。

写在最后

单例模式最为常用的一种模式,有其自己的优势和适用场景。如果一个类型在程序中要求实例化的数量有要求的,该怎么办呢?比如,一个类型可以最多实例化10个,或者每个线程可以实例化一个,你可能需要研究一下threadLocal 或者hashmap等知识了。至于集群间的单例实现欢迎大家在留言区体现!!

更多精彩文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容