C#-设计模式-单例模式

C#-设计模式-单例模式

C#-单例模式

一、前言

二、 单例模式的介绍

从“单例”字面意思上理解为——一个类只有一个实例,所以单例模式也就是保证一个类只有一个实例的一种实现方法罢了(设计模式其实就是帮助我们解决实际开发过程中的方法, 该方法是为了降低对象之间的耦合度,然而解决方法有很多种,所以前人就总结了一些常用的解决方法为书籍,从而把这本书就称为设计模式),下面给出单例模式的一个官方定义:**确保一个类只有一个实例,并提供一个全局访问点。**为了帮助大家更好地理解单例模式,大家可以结合下面的类图来进行理解,以及后面也会剖析单例模式的实现思路:

C#-设计模式-单例模式_第1张图片

三、为什么会有单例模式

从单例模式的定义中我们可以看出——单例模式的使用自然是当我们的系统中某个对象只需要一个实例的情况,例如:操作系统中只能有一个任务管理器,操作文件时,同一时间内只允许一个实例对其操作等,既然现实生活中有这样的应用场景,自然在软件设计领域必须有这样的解决方案了(因为软件设计也是现实生活中的抽象),所以也就有了单例模式了。

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

为什么前人会这样去实现单例模式的呢?他们是如何思考的呢?后面经过自己的琢磨也就慢慢理清楚单例模式的实现思路了,并且此时也不再觉得单例模式陌生了,下面就分享我的一个剖析过程的:

我们从单例模式的概念(**确保一个类只有一个实例,并提供一个访问它的全局访问点)**入手,可以把概念进行拆分为两部分:(1)确保一个类只有一个实例;

(注:这样定义私有构造函数就是上面的一个思考过程的,要创建实例,自然就要有一个变量来保存该实例把,所以就有了私有变量的声明,但是实现中是定义静态私有变量,朋友们有没有想过——这里为什么定义为静态的呢?对于这个疑问的解释为:每个线程都有自己的线程栈,定义为静态主要是为了在多线程确保类有一个实例

(注:这样就有了公有方法的定义了,该方法就是提供方法问类的全局访问点)

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

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

        /// 
        /// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点
        /// 
        /// 
        public static Singleton GetInstance()
        {
            // 如果类的实例不存在则创建,否则直接返回
            if (uniqueInstance == null)
            {
                uniqueInstance = new Singleton();
            }
            return uniqueInstance;
        }
    }

上面的单例模式的实现在单线程下确实是完美的,然而在多线程的情况下会得到多个Singleton实例,因为在两个线程同时运行GetInstance方法时,此时两个线程判断(uniqueInstance ==null)这个条件时都返回真,此时两个线程就都会创建Singleton的实例,这样就违背了我们单例模式初衷了,既然上面的实现会运行多个线程执行,那我们对于多线程的解决方案自然就是使GetInstance方法在同一时间只运行一个线程运行就好了,也就是我们线程同步的问题了,具体的解决多线程的代码如下:

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

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

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

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

            return uniqueInstance;
        }
    }

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

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

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

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

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

五、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);
        }
    }

六、总结

一、主要优点:
  • 1、提供了对唯一实例的受控访问。
  • 2、由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
  • 3、允许可变数目的实例。
二、主要缺点:

1、由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
2、单例类的职责过重,在一定程度上违背了“单一职责原则”。
3、滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

三、适用场景

在以下情况下可以考虑使用单例模式:

  • (1) 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。
  • (2) 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
四、单线程的单例模式
        public sealed class Singleton
        {
            public static Singleton instance = null;
            private Singleton() { }
            public static Singleton Instance
            {
                get
                {
                    if (instance == null)
                        instance = new Singleton();
                    return instance;
                }
            }
        }

实现很简单,理解起来也不难,但是有个致命的缺点:线程不安全,两个线程可能同事检测到 instance==null成立,因为有可能在一瞬间创建两个。其实在多线程运行时,即使已经在内存中创建了实例,但是内存模型并不能保证新的instance能够看见其它线程所创建的实例。因此这种方法不推荐。下面看看线程安全的方法:

五、简单线程安全
 public sealed class Singleton2
        {
            private static Singleton2 instance = null;
            private static readonly object padlock = new object();
 
            private Singleton2() { }
            public static Singleton2 Instance
            {
                get
                {
                    lock(padlock)
                    {
                        if (instance == null)
                            instance = new Singleton2();
                        return instance;
                    }
                }
            }
        }

变化不大,基于前面的分析,这个只是添加了一个object成员以此来实现锁。这种方法是线程安全的,每个线程要创建实例时,都要先去得锁,然后再判断是否为空,也就是保证多线程运行时,只有一个线程能进行进行实例化,而一旦一个线程实例化后,后面的线程取到锁后,就会发现实例已经创建。但是这个方法也有一个明显的缺点:假设线程足够多,100个吧,每一个线程在进行if(instancenull)判断时,都到等到某一个线程退出后将锁获得。所以会导致性能问题。改进的办法就是多进行以此判断,如果instancenull是FALSE,那自然没必要在等待锁,再去判断一次。

六、双层验证下的多线程安全
 public sealed class Singleton2
        {
            private static Singleton2 instance = null;
            private static readonly object padlock = new object();
 
            private Singleton2() { }
            public static Singleton2 Instance
            {
                get
                {
                    if(null==instance)
                    {
                        lock (padlock)
                        {
                            if (instance == null)
                                instance = new Singleton2();
                            
                        }
                    }
 
                    return instance;
                }
            }
        }
七、更高效的单例模式——饿汉模式
        public sealed class Singleton3
        {
            private static readonly Singleton3 instance = new Singleton3();
 
            static Singleton3() { }
            private Singleton3() { }
 
            public static Singleton3 Instance
            {
                get
                {
                    return instance;
                }
            }
 
 
        }

如你看到的,这个似乎比前面两种实现都要简单,它是如何保证线程安全的呢?对于每个AppDomain,在C#中类的实例被构造或者静态成员被引用时,静态构造函数才会执行,而且只执行一次。由于这个类在一加载就被实例化,因此,效率上要比前面实现高的多 那为何叫饿汉模式呢?这是因为相对于第2、3的实现方式,Singleton必选被引用一次后才会将自己实例化,这种成为懒汉式;而当下,无论我们在程序中用不用,编译器都会实例化一个对象,所以就成为“饿汉模式”

那我们看另外一种懒汉模式:

八、懒汉模式——利用嵌套类
public sealed class Singleton
{
    private Singleton()
    {
    }
 
    public static Singleton Instance { get { return Nested.instance; } }
 
    private class Nested
    {
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static Nested()
        {
        }
 
        internal static readonly Singleton instance = new Singleton();
    }
}

当嵌套类的静态成员第一次被引用到时,就会触发实例化,因此这是一种懒汉式单例模式。但是有着前面所有模式的有点,因此相当推荐!请注意,尽管嵌套类可以访问封闭类的私有成员,但情况并非如此,因此这里实例需要是内部的。不过,这不会引起任何其他问题,因为类本身是私有的。然而,为了使实例化变得懒惰,代码要复杂一些。

九、 使用net4之后的Lazy泛型

在.net4之后,可以使用System.Lazy类型实现迟实例化,你唯一需要做的就是传递一个代理便令给单例类的构造函数,可以用lambda表达式轻松实现:

public sealed class Singleton
{
    private static readonly Lazy<Singleton> lazy =
        new Lazy<Singleton>(() => new Singleton());
 
    public static Singleton Instance { get { return lazy.Value; } }
 
    private Singleton()
    {
    }
}

et4之后,可以使用System.Lazy类型实现迟实例化,你唯一需要做的就是传递一个代理便令给单例类的构造函数,可以用lambda表达式轻松实现:

public sealed class Singleton
{
    private static readonly Lazy<Singleton> lazy =
        new Lazy<Singleton>(() => new Singleton());
 
    public static Singleton Instance { get { return lazy.Value; } }
 
    private Singleton()
    {
    }
}

你可能感兴趣的:(☀️C#-The,World,c#,设计模式,单例模式)