24、面向对象的设计模式之单例模式--基础概念及实现

单例模式的定义如下:

  单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类成为单例类,它提供全局访问的方法。单例模式是一种对象创建型模型。

 

单例模式的分类:

  饿汉单例

  懒汉单例

  单线程单例

  多线程单例

 

饿汉单例:

public class EagerSingleton
{
    // 由于在定义变量的时候实例化单例类,在类加载时就已经创建了单个对象。
    private static EagerSingleton instance = new EagerSingleton();
    private EagerSingleton()
    {

    }
    public static EagerSingleton GetInstance()
    {
        return instance;
    }
}

懒汉单例:

//在第一次调用GetInstance()方法时实例化,在类加载并不自行实例化。这种技术叫做延迟加载(Lazy Load),就是在需要的时候在加载实例。
//为了避免多线程调用GetInstance方法。加lock
public class LazySingleton
{
    private static LazySingleton instance = null;
    private LazySingleton()
    {

    }
    public static LazySingleton GetInstance()
    {
        if(instance == null)
        {
            new LazySingleton();
        }
        return instance;
    }
}

单线程单例模式:

public class SingleThreadSingleton
{
    private static SingleThreadSingleton instance = null;
    private SingleThreadSingleton()
    {

    }

    public static SingleThreadSingleton GetInstance()
    {
        if(instance == null)
        {
            instance = new SingleThreadSingleton();
        }
        return instance;
    }
}

多线程单例:

public class MultiThreadSingleton
{
    private static MultiThreadSingleton instance;
    private static readonly object syncRoot = new object();//静态的只读的辅助对象
    private MultiThreadSingleton()
    {

    }

    public static MultiThreadSingleton GetInstance()
    {
        lock (syncRoot) // 同一时刻枷锁的这部分程序只有一个线程进入。但是有一个问题这样会影响效率
        {
            if(instance == null)
            {
                instance = new MultiThreadSingleton();
            }
        }
        return instance;
    }
}

多线程实例改良版: (volatile的作用和用法详见-C#学习——volatile关键字

public class MultiThreadSingleton
{
    //  volatile 保证严格意义的多线程编译器在代码编译时对指令不进行微调
    private static volatile MultiThreadSingleton instance = null;
    private static readonly object syscRoot = new object();
    private MultiThreadSingleton()
    {

    }
    public static MultiThreadSingleton GetInstance()
    {
        
        if(instance == null)// 多个线程调用GetInstance方法时,都可以通过第一重if(instance == null),可以提升效率
        {
            lock (syscRoot) //只有一个线程时进入
            {
                if(instance == null) // 如果没有这层,第一个线程创建了实例,第二个线程还可以创建实例。
                {
                    instance = new MultiThreadSingleton();
                }
            }
        }
        return instance;
    }
}

静态单例模式:

public class StaticSingleton
{
    public static readonly StaticSingleton instance = new StaticSingleton();
    private StaticSingleton()
    {

    }
}
public class StaticSingleton
{
    public static readonly StaticSingleton instance;
    private StaticSingleton()
    {

    }

    static StaticSingleton()
    {
        instance = new StaticSingleton();
    }
}

饿汉单例与懒汉单例类比较

  饿汉单例在类被加载时就将自己实例化,它的优点在于无需考虑多线程访问问题,可以保证实例的唯一性;从调用速度和反应时间角度来讲,由于单例对象一开始就得以创建,因此要优于懒汉式单例。但是无论系统在运行时是否需要使用该单例对象,由于在类加载时该对象就需要创建,因此从资源利用效率角度来讲,饿汉式单例不及懒汉式单例,而且在系统加载时由于需要创建饿汉式单例对象,加载时间可能会比较长

  懒汉式单例类在第一次使用时被创建,无需一直占用系统资源,实现了延迟加载,但是必须处理好多线程同时访问的问题,特别是当单例类作为资源控制器,在实例化时必然涉及资源初始化,而资源初始化很有可能耗费大量时间,这意味着出现多线程同时首次引用此类的几率变得较大,需要通过双重锁定机制进行控制,这将导致系统性能受到影响

一种更好的单例实现方法

  饿汉式单例不能实现延迟加载,不管将来用不用始终占据内存;懒汉式单例类线程安全控制繁琐,而且性能受影响。可见无论是饿汉式单例还是懒汉式单例都存在这样那样的问题,有没有一种方法,能够将两种单例的缺点都克服,而将两者的优点合二为一呢?答案是:Yes!下面我们学习一种更好的被称之为Initialization Demand Holder(初始化需求持有人 loDH)的技术。

  在loDH中,我们在单例中增加了一个静态(static)内部类,在该内部类中创建单例对象,再将该单例对象通过getInstance()方法返回给外部使用,实现代码如下所示:

  

public class Singleton
{
    private Singleton()
    {

    }
    
    private static class HolderClass
    {
        public static Singleton instance = new Singleton();
    }

    public static Singleton GetInstance()
    {
        return HolderClass.instance;
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        Singleton s1, s2;
        s1 = Singleton.GetInstance();
        s2 = Singleton.GetInstance();
        Console.WriteLine(s1 == s2);
    }
}

  编译并运行上述代码,运行结果为:True,即创建的对象s1和s2为同一对象。由于静态单例对象没有作为Singleton的成员变量进行直接实例化,因此类加载时不会实例化Singleton,第一次调用getInstance()时将加载内部类HolderClass,该内部中类定义了一个static类型的变量instance,此时会首先初始化这个成员变量,由C#公共运行时(Java中是Java虚拟机)来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()方法没有任何线程锁定,因此其性能不会造成任何影响。

posted @ 2020-06-09 19:18  三里路异乡客  阅读(212)  评论(0编辑  收藏  举报