软件设计中的单例模式开发

来源:互联网 发布:小苍的淘宝店铺 编辑:程序博客网 时间:2024/05/21 05:08

一:单例模式介绍:

        单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源

        如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。

       显然单例模式的要点有三个。一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。

从具体实现角度来说,就是以下三点:一是单例模式的类只提供私有的构造函数二是类定义中含有一个该类的静态私有对象三是该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。

二、采用单例模式的动机。

      单例模式可以起到以下作用:

     第一、控制资源的使用,通过线程同步来控制资源的并发访问;

     第二、控制实例产生的数量,达到节约内存目的。在资源共享的情况下,避免由于资源操作时导致的性能或损耗等

     第三、实现数据共享,它可以在不建立直接关联的条件下,让多个不相关的两个线程或者进程之间实现通信。

    下述是单例模式的使用场景:

             1. WindowsTaskManager(任务管理器)就是很典型的单例模式。想想看,是不是呢,你能打开两个windowstask manager吗?不信你自己试试看哦

2. windowsRecycleBin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
3. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
4. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
5. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
6. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
7. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。

8. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。

             9. HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例.

         如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。

三、单例模式的实现。

        C# 实现

namespaceSingleton
  {
    public classSingleton
    {
      //定义一个该类的静态私有对象来保存该类的唯一实例
      private static Singleton singleton;

//定义一个只读静态对象,且这个对象是在程序运行时创建的。
       privatestatic readonly object syncObject = new object();

// 构造函数,为空函数,是私有的。外部无法使用 new来创建该类的实例。

private Singleton()
      { }

// 定义一个全局访问设置为静态方法, 在类的外部无需实例化

//就可以调用该方法
   public static Singleton GetInstance()
   {
   //这里可以保证只实例化一次,即在第一次调用时实例化,以后调用便不会再实例化

//第一重 singleton == null
    if (singleton== null)
    {

//加锁
    lock(syncObject)
    {
    //第二重 singleton == null
     if (singleton == null)
      {
       singleton = new Singleton();
      }
     }
    }
    returnsingleton;
   }
  }
  }

        上述代码的实现,使用了双重锁机制。何要使用双重检查锁定呢?原因如下:

如果有两个线程同时到达,即同时调用 GetInstance () 方法。

         此时由于 singleton == null ,所以很明显,两个线程都可以通过第一重的singleTon == null 。

进入第一重 if 语句后,由于存在锁机制,所以会只有一个线程进入 lock 语句并进入第二重 singleTon == null ,且需要执行完毕后才可以让出CPU。

而另外的一个线程则会在 lock 语句的外面等待,直到第一个线程执行完初始化对象。

       而当第一个线程执行完 new  SingleTon()语句后,便会退出锁定区域,此时,第二个线程便可以进入 lock 语句块,

      此时,如果没有第二重 singleTon == null 的话,那么第二个线程还是可以调用 new  SingleTon ()语句,这样第二个线程也会创建一个 SingleTon实例,这样也还是违背了单例模式的初衷的。所以这里必须要使用双重检查锁定。

如果我去掉第一重 singleton == null ,程序还是可以在多线程下完好的运行的,考虑在没有第一重 singleton == null 的情况下,当有两个线程同时到达,此时,由于lock 机制的存在,第一个线程会进入 lock 语句块,并且可以顺利执行 new SingleTon(),当第一个线程退出 lock 语句块时, singleTon 这个静态变量已不为 null 了,所以当第二个线程进入 lock 后,还是会被第二重 singleton == null 挡在外面,而无法执行 new Singleton(),所以在没有第一重 singleton == null 的情况下,也是可以实现单例模式的。

      那么为什么需要第一重 singleton == null 呢?

     这里就涉及一个性能问题了,因为对于单例模式的话,new SingleTon()只需要执行一次就 OK 了,而如果没有第一重 singleTon == null 的话,每一次有线程进入 getInstance()时,均会执行锁定操作来实现线程同步,需要内核切换,lock机制背后实际上需要操作系统接管执行,背后机制很复杂,导致程序执行非常耗费性能,而如果我加上第一重 singleTon == null 的话,那么就只有在第一次,也就是singleTton ==null 成立时的情况下执行一次锁定以实现线程同步,而以后的话,便只要直接返回Singleton 实例就 OK 了而根本无需再进入 lock语句块了,这样就可以解决由线程同步带来的性能问题了。

       C++实现(没有考虑锁机制,只是用来例子展示)

class CSingleton

{

private:

CSingleton() //构造函数是私有的

{

}

public:

staticCSingleton * GetInstance()

{

staticCSingleton *m_pInstance;

if(m_pInstance== NULL) //判断是否第一次调用

m_pInstance =new CSingleton();

returnm_pInstance;

}

};

 

 

1 0
原创粉丝点击