您的位置:首页 > 编程语言 > C#

【设计模式】C#版单例模式实例讲解

2017-02-04 16:52 543 查看
引言

        单例模式是最常用到的设计模式之一。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方法简化了在复杂环境下的配置管理、

简介

        单例模式就是指一个类永远只能实例化一次。使用的方式是调用类里创建的静态方法。通常来说,单例模式创建的类,都是不带形参的,原因就是创建多个实例的时候,如果参数不同的话,那么就会造成一些不必要的问题

单例模式基本实现思路

        单例模式要求类能够有返回对象一个引用和一个获得实例的方法(必须是静态方法,通常使用getInstance这个名称)

        单例模式的实现主要是通过以下步骤:

        1、将该类的构造方法定义为私有方法,这样其他处的代码就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例

        2、在该类内提供一个静态方法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用

第一版--非线程安全

       
///
/// 单例模式的实现
///
//不要用这种方式
public sealed class Singleton
{
// 定义一个静态变量来保存类的实例
private static Singleton instance;
// 定义私有构造函数,使外界不能创建该类实例
private Singleton()
{}

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


        上面的单例模式的实现唉单线程下确实是完美的。上面的方法是非线程安全的,2个不同的线程可以同事进入这个方法,如果instance为空的并且这里返回真的情况下,都可以创建实例,这显然违反了单例模式,实际上,在测试以前,实例就已经可能被创建了,但是内存模型不能保证这个实例被其他的线程看到,除非合适的内存屏障已经被跨过了

第二版--简单线程安全

       
///
/// 单例模式的实现
///
public class Singleton
{
// 定义一个静态变量来保存类的实例
private static Singleton Instance;
// 定义一个标识确保线程同步
private static readonly object syncRoot = new object();
// 定义私有构造函数,使外界不能创建该类实例
private Singleton()
{
}
///
/// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点
///
public static Singleton GetInstance()
{
// 当第一个线程运行到这里时,此时会对syncRoot对象 "加锁",
// 当第二个线程运行该方法时,首先检测到syncRoot对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
// lock语句运行完之后(即线程运行完之后)会对该对象"解锁"
lock (syncRoot)
{
// 如果类的实例不存在则创建,否则直接返回
if (Instance == null)
{
Instance = new Singleton();
}
}
return Instance;
}
}
      

        上述实现的线程是安全的。这个线程在共享的object上取出了一把锁,然后在创建实例以前检查这个实例是否被创建了。这个保护了内存屏障问题(lock保证了所有的读取操作是在LOCK获得以后发生的,所有的unlock保证了所有的写操作在lock 释放以后发生的),这样就保证了一个线程只能创建一个实例(每次只有一个线程在这段代码中运行)

第三版--双重锁定

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

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

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

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


        在第二版的代码中,每个线程都会对线程辅助对象syncRoot加锁之后再判断实例是否存在,对于这个操作其实是没有必要的,因为当第一个线程创建了该类的示例之后,后面的线程此时只需要判断(Instance==null)为假,此时完全没有必要对线程辅助对象加锁之后再去判断。为了改进上面实现方式的缺陷,我们只需要在lock语句前面加上一句(Instance==null)的判断就可以避免锁所增加的额外开销

单例模式的应用

1、应用场景

       (1)Windows的任务管理器就是很典型的单例模式。想想看,你能同时打开两个任务管理器吗?

       (2)Windows的回收站也是典型的单例模式的应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例

       (3)网站的计数器,一般也是采用单例模式实现,否则难以同步

2、单例模式应用场景一般发生在以下条件下:

      (1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如日志文件,应用配置等

      (2)控制资源的情况下,方便资源之间的相互通信。如线程池等

单例模式的优缺点

优点:

        1、提供了对唯一实例的受控访问

        2、由于系统内存中只存在一个对象,因此可以节约系统资源

        3、允许可变数目的实例

缺点:

       1、由于单例模式中没有抽象层,因此单例类的扩展有很大的困难

       2、单例类的职责过重,在一定程度上违背了“单一职责原则”

结语

        看完单例模式的介绍,可能有的人会有这样的疑问--为什么要有单例模式?单例模式是在什么情况下使用的?从单例模式的定义中我们可以看出--单例模式的使用是在当系统中某个对象需要一个实例的情况。现实生活中有这样的应用场景,自然在软件设计领域必须有这样的解决方案了,因为软件设计也是现实生活中的抽象,所以就有单例模式了

    

      本文只是对基础知识做一个小小的总结,不深究。如有不同,见解欢迎指正

       本文所有代码均已通过作者测试

       本文所有内容均为作者原创,如有转载,请注明出处
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: