vlambda博客
学习文章列表

程序员羽化之路--我眼中的单例模式并不完美(文末送书)


点击上方蓝字  免费领书
程序员羽化之路--我眼中的单例模式并不完美(文末送书)

菜菜哥,帮忙解决一个问题,我写的一个程序领导说有线程安全问题,改不好不允许上线

程序员羽化之路--我眼中的单例模式并不完美(文末送书)
程序员羽化之路--我眼中的单例模式并不完美(文末送书)

什么样的程序呢?

程序员羽化之路--我眼中的单例模式并不完美(文末送书)
程序员羽化之路--我眼中的单例模式并不完美(文末送书)
/// <summary>
    /// 全局唯一的配置信息
    /// </summary>
   public class Config
    {
        private static Config _config = null;
        public static Config GetConfig()
        
{
            if (_config == null)
            {
                _config = new Config();
            }
            return _config;
        }
    }

我的程序中有一个全局唯一的配置类,用来存储配置信息

程序员羽化之路--我眼中的单例模式并不完美(文末送书)
程序员羽化之路--我眼中的单例模式并不完美(文末送书)

看样子你是需要一个单例类吧,但是你这个代码对于实现单例模式有很大问题啊

程序员羽化之路--我眼中的单例模式并不完美(文末送书)
程序员羽化之路--我眼中的单例模式并不完美(文末送书)

01
PART
单例模式

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

程序员羽化之路--我眼中的单例模式并不完美(文末送书)

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


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


0 2
PART
单例实现
程序员羽化之路--我眼中的单例模式并不完美(文末送书)

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


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的内部类,静态初始化特性等都可以实现线程安全的单例模式。

单例模式居然还有这么多事,那以后我要深入研究一下了

程序员羽化之路--我眼中的单例模式并不完美(文末送书)
程序员羽化之路--我眼中的单例模式并不完美(文末送书)

单例模式虽然是一种比较常用的模式,但是从很多使用维度来说,有很多问题

程序员羽化之路--我眼中的单例模式并不完美(文末送书)
程序员羽化之路--我眼中的单例模式并不完美(文末送书)

03
PART
单例模式缺陷
程序员羽化之路--我眼中的单例模式并不完美(文末送书)

1. 面向对象设计讲究封装,继承,多态,以及抽象。单例模式对于其中的继承,多态支持得不好,抽象讲究的是面向接口编程,单例模式并没有接口概念。拿以上配置文件的单例为例,假设现在的配置信息是以本地文件的方式进行加载,如果后期要加入从数据库加载配置信息这个需求,单例模式必须修改现有代码,这在一定程度上就违反了设计规则。所以单例在一定程度上丢失了应对未来需求的扩张性。

2. 单例模式在职责上有时候会过重,即要负责初始化的过程,又要负责初始化的内容,甚至在某些情况下还要负责其他程序,这在一定程度上违反了“单一职责”原则。

3. 由于单例模式对外之后一个入口点,并没有显示的利用构造函数传参的方式进行初始化,内部使用了哪些类型并不能很快识别出来,开发人员很难识别出类的依赖关系

4. 单例模式并不适合那些表面是单例,但是未来还有可能扩展的场景。举个栗子:线程池在很多程序中都被设计成单例模式,很多开发人员认为程序中只存在一个线程池,但是在个别需求下,同一个程序需要多个线程池的场景是存在的。


04
PART
写在最后
程序员羽化之路--我眼中的单例模式并不完美(文末送书)

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


程序员羽化之路--我眼中的单例模式并不完美(文末送书)

程序员羽化之路--我眼中的单例模式并不完美(文末送书)

程序员羽化之路--我眼中的单例模式并不完美(文末送书)

长按添加菜菜好友
程序员羽化之路--我眼中的单例模式并不完美(文末送书)
关注后回复:“ 大礼包 ”和“福利”,领取惊喜


福利时间( 送1本
分布式系统架构

分布式架构已成为IT系统中使用最普遍的架构设计方式之一,市场上关于架构的书籍很多,但是内容都比较零散,讲解如何落地的书籍更少。本书分别从软件架构原理、架构设计与优化的过程、高可用中间件的实践等多个维度对分布式系统的架构进行了阐述、分析和整合 。


从前端到后端、从高效交互到负载均衡、从网络传输到Web服务器、从高并发到高可用……本书囊括了分布式系统的整个技术体系,内容详实、结构清晰,能帮助读者理解和掌握分布式系统架构设计的难点和调优方案。

非常感谢华章科技

对本次活动的大力支持

长按以下菊花码参加活动