【设计模式】单例模式 Singleton Pattern

一般咱们在写程序的时候会碰到一个类只容许在整个系统中只存在一个实例(Instance)  的状况, 好比说咱们想作一计数器,统计某些接口调用的次数,一般咱们的数据库链接也是只指望有一个实例。Windows系统的系统任务管理器也是始终只有一个,若是你打开了windows管理器,你再想打开一个那么他仍是同一个界面(同一个实例), 还有好比 作.Net平台的人都知道,AppDomain 对象,一个系统中也只有一个,全部的类库都会加载到AppDomain中去运行。只须要一个实例对象的场景,随处可见,那么有么有什么好的解决方法来应对呢? 有的,那就是 单例模式。数据库

1、单例模式定义

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

2、单例模式结构图

image

  • Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的GetInstance()工厂方法,让客户能够访问它的惟一实例;为了防止在外部对其实例化,将其构造函数设计为私有(private);在单例类内部定义了一个Singleton类型的静态对象,做为外部共享的惟一实例。

3、 单例模式典型代码

public class Singleton
{
    private static Singleton instance;
    private Singleton()
    {
    }
    public static Singleton GetInstance()
    {
        if(instance==null)
        {
            instance=new Singleton();
        }

        return instance;
    }
}

客户端调用代码:安全

static void Main(string[] args)
{
    Singleton singleto = Singleton.GetInstance();
}

在C#中常常将统一访问点暴露出一个只读的属性供客户端程序使用,这样代码就变成了这样:网络

public class Singleton
{
    private static Singleton instance;
    private Singleton()
    {
    }
    public static Singleton GetInstance
    {
        get
        {
            if (instance == null)
            {
                instance = new Singleton();
            }

            return instance;
        }
    }
}

客户端调用:多线程

static void Main(string[] args)
{
    Singleton singleton = Singleton.GetInstance;
}

4、单例模式实例

1. 懒汉模式

假如咱们要作一个程序计数器,一旦程序启动不管多少个客户端调用这个 计数器计数的结果始终都是在前一个的基础上加1,那么这个计数器类就能够设计成一个单例模式的类。并发

public class SingletonCounter
{
    private static SingletonCounter instance;
    private static int number=0;
    private SingletonCounter() { }
    public static SingletonCounter Instance
    {
        get
        {
            if (instance == null) instance = new SingletonCounter();

            number++;
            return instance;
        }
    }

    public int GetCounter(){
        return number;
    }
}

客户端调用:函数

static void Main(string[] args)
{
    //App A call the counter;
    SingletonCounter singletonA = SingletonCounter.Instance;
    int numberA = singletonA.GetCounter();
    Console.WriteLine("App A call the counter get number was:" + numberA);

    //App B call the counter;
    SingletonCounter singletonB = SingletonCounter.Instance;
    int numberB = singletonA.GetCounter();
    Console.WriteLine("App B call the counter get number was:" + numberB);

    Console.ReadKey();
}

输出结果:高并发

image

这个实现是线程不安全的,若是有多个线程同时调用,而且又偏偏在计数器初始化的瞬间多个线程同时检测到了 instance==null为true状况,会怎样呢?这就是下面要讨论的 “加锁懒汉模式”性能

二、加锁懒汉模式

多个线程同时调用而且同时检测到 instance == null 为 true的状况,那后果就是会出现多个实例了,那么就没法保证惟一实例了,解决这个问题就是增长一个对象锁来确保在建立的过程当中只有一个实例。(锁能够确保锁住的代码块是线程独占访问的,若是一个线程占有了这个锁,其它线程只能等待该线程释放锁之后才能继续访问)。spa

public class SingletonCounter
{
    private static SingletonCounter instance;
    private static readonly object locker = new object();
    private static int number = 0;
    private SingletonCounter() { }
    public static SingletonCounter Instance
    {
        get
        {
            lock (locker)
            {
                if (instance == null) instance = new SingletonCounter();

                number++;
                return instance;
            }
        }
    }

    public int GetCounter()
    {
        return number;
    }
}

客户端调用代码:

static void Main(string[] args)
{ 
    for (int i = 1; i < 100; i++)
    {
        var task = new Task(() =>
        {
            SingletonCounter singleton = SingletonCounter.Instance;
            int number = singleton.GetCounter();

            Console.WriteLine("App  call  the counter get number was:" +  number);

        });
        task.Start();
    }
    Console.ReadKey();
}

输出结果:

image

这种模式是线程安全,即便在多线程的状况下仍然能够保持单个实例。那么这种模式会不会有什么问题呢?假如系统的访问量很是大,并发很是高,那么计数器就会是一个性能瓶颈,由于对锁会使其它的线程没法访问。在访问量不大,并发量不高的系统尚可应付,若是高访问量,高并发的状况下这样作确定是不行的,那么有什么办法改进呢?这就是下面要讨论的“双检查加锁懒汉模式”。

三、双检查加锁懒汉模式

加锁懒汉模式虽然保证了系统的线程安全,可是却为系统带来了新能问题,主要的性能来自锁带来开销,双检查就是解决这个锁带来的问题,在锁以前再作一次 instance==null的检查,若是返回true就直接返回 单例对象了,避开了无谓的锁, 咱们来看下,双检查懒汉模式代码:

public class DoubleCheckLockSingletonCounter
{
    private static DoubleCheckLockSingletonCounter instance;
    private static readonly object locker = new object();
    private static int number = 0;
    private DoubleCheckLockSingletonCounter() { }
    public static DoubleCheckLockSingletonCounter Instance
    {
        get
        {
            if (instance == null)
            {
                lock (locker)
                {
                    if (instance == null)
                    {
                        instance = new DoubleCheckLockSingletonCounter();
                    }
                }
            }
            number++;
            return instance;
        }
    }

    public int GetCounter()
    {
        return number;
    }
}

客户端调用代码和“懒汉加锁模式”相同,输出结果也相同。

四、饿汉模式

单例模式除了咱们上面讲的三种懒汉模式外,还有一种叫“饿汉模式”的实现方式,“饿汉模式”直接在Singleton类里实例化了当前类的实例,而且保存在一个静态对象中,由于是静态对象,因此在程序启动的时候就已经实例化好了,后面直接使用,所以不存在线程安全的问题。

下面是“饿汉模式”的代码实现:

public class EagerSingletonCounter
{
    private static EagerSingletonCounter instance = new EagerSingletonCounter();

    private static int number = 0;
    private EagerSingletonCounter() { }
    public static EagerSingletonCounter Instance
    {
        get
        {
            number++;
            return instance;
        }
    }

    public int GetCounter()
    {
        return number;
    }
}

 

5、单例模式应用场景

单例模式只有一个角色很是简单,使用的场景也很明确,就是一个类只须要、且只能须要一个实例的时候使用单例模式。

6、扩展

 

一、”饿汉模式“和”懒汉模式“的比较

”饿汉模式“在程序启动的时候就已经实例化好了,而且一直驻留在系统中,客户程序调用很是快,由于它是静态变量,虽然完美的保证线程的安全,可是若是建立对象的过程很复杂,要占领系统或者网络的一些昂贵的资源,可是在系统中使用的频率又极低,甚至系统运行起来后都不会去使用该功能,那么这样一来,启动以后就一直占领着系统的资源不释放,这有些得不偿失。

“懒汉模式“ 刚好解决了”饿汉模式“这种占用资源的问题,”懒汉模式”将类的实例化延迟到了运行时,在使用时的第一次调用时才建立出来并一直驻留在系统中,可是为了解决线程安全问题, 使用对象锁也是 影响了系统的性能。这两种模式各有各的好处,可是又各有其缺点。

有没有一种折中的方法既能够避免一开始就实例化且一直占领系统资源,又没有性能问题的Singleton呢? 答案是:有的。

二、第三种选择

“饿汉模式“类不能实现延迟加载,无论用不用始终占据内存;”懒汉式模式“类线程安全控制烦琐,并且性能受影响。咱们用一种折中的方法来解决这个问题,针对主要矛盾, 即:既能够延时加载又不影响性能。

在Singleton的内部建立一个私有的静态类用于充当单例类的”初始化器“,专门用来建立Singleton的实例:

public class BestPracticeSingletonCounter
{
    private static class SingletonInitializer{
        public static BestPracticeSingletonCounter instance = new BestPracticeSingletonCounter();
    } 
    
    private static int number = 0;
    private BestPracticeSingletonCounter() { }
    public static BestPracticeSingletonCounter Instance
    {
        get
        {
            number++;
            return SingletonInitializer.instance;
        }
    }

    public int GetCounter()
    {
        return number;
    }
}

这种模式兼具了”饿汉“和”懒汉“模式的优势有摒弃了其缺点,能够说是一个完美的实现。

相关文章
相关标签/搜索