代码改变世界

单例模式

2012-05-29 20:39  刘永强  阅读(125)  评论(0)    收藏  举报

经典模式:简单说来,单例模式(也叫单件模式)的作用就是保证在整个应用程序的生命周期中,

 

任何一个时刻,单例类的实例都只存在一个(当然也可以不存在)

1)首先,该Singleton的构造函数必须是私有的,以保证客户程序不会通过new()操作产生一个实例,达到实现单例的目的;

  2)因为静态变量的生命周期跟整个应用程序的生命周期是一样的,所以可以定义一个私有的静态全局变量instance来保存该类的唯一实例;

  3)必须提供一个全局函数访问获得该实例,并且在该函数提供控制实例数量的功能,即通过if语句判断instance是否已被实例化,如果没有则可以同new()创建一个实例;否则,直接向客户返回一个实例。

  在这种经典模式下,没有考虑线程并发获取实例问题,即可能出现两个线程同时获取instance实例,且此时其为null时,就会出现两个线程分别创建了instance,违反了单例规则。(见备注)

经典模式:

public class Singleton

    {

        //定义一个私有的静态全局变量来保存该类的唯一实例

        private static Singleton singleton;

 

        /// <summary>

        /// 构造函数必须是私有的

        /// 这样在外部便无法使用 new 来创建该类的实例

        /// </summary>

        private Singleton()

        {

        }

 

       /// <summary>

        /// 定义一个全局访问点

        /// 设置为静态方法

        /// 则在类的外部便无需实例化就可以调用该方法

        /// </summary>

        /// <returns></returns>

        public static Singleton GetInstance()

        {

            //这里可以保证只实例化一次

            //即在第一次调用时实例化

            //以后调用便不会再实例化

            if (singleton == null)

            {

                singleton = new Singleton();

            }

            return singleton;

        }

    }

•多线程下的单例模式
•1、Lazy模式:使用双重锁方式较好地解决了多线程下的单例模式实现。先看内层的if语句块,使用这个语句块时,先进行加锁操作,保证只有一个线程可以访问该语句块,进而保证只创建了一个实例。再看外层的if语句块,这使得每个线程欲获取实例时不必每次都得加锁,因为只有实例为空时(即需要创建一个实例),才需加锁创建,若果已存在一个实例,就直接返回该实例,节省了性能开销。(见备注)
•2、饿汉式单例  这种模式的特点是自己主动实例。(见备注)

1

public class Singleton

{

   private static Singleton instance;

  private static object _lock=new object();

  private Singleton()

  {}

  public static Singleton GetInstance()

  {

  if(instance==null)

  {

  lock(_lock);

  if(instance==null)

  {

  instance=new Singleton();

  }

  }

  return instance;

  }

}

这里需要说明的是,为何还要创建一个 syncObject 静态只读对象呢?

 

由于提供给 lock 关键字的参数必须为基于引用类型的对象,该对象用来定义锁的范围,

 

所以这个引用类型的对象总不能为 null 吧,而一开始的时候,singleton 为 null ,所以是无法实现加锁的,

 

所以必须要再创建一个对象即 syncObject 来定义加锁的范围。

 

还有要解释一下的就是在 GetInstance()中,我为什么要在 if 语句中使用两次判断 singleton == null ,

 

这里涉及到一个名词 Double-Check Locking ,也就是双重检查锁定,

 

为何要使用双重检查锁定呢?

 

考虑这样一种情况,就是有两个线程同时到达,即同时调用 GetInstance(),

 

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

 

进入第一重 if 语句后,由于存在锁机制,所以会有一个线程进入 lock 语句并进入第二重 singleton == null ,

 

而另外的一个线程则会在 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()时,均会执行锁定操作来实现线程同步,

 

这是非常耗费性能的,而如果我加上第一重 singleton == null 的话,

 

那么就只有在第一次,也就是 singleton ==null 成立时的情况下执行一次锁定以实现线程同步,

 

而以后的话,便只要直接返回 Singleton 实例就 OK 了而根本无需再进入 lock 语句块了,这样就可以解决由线程同步带来的性能问题了。好,关于多线程下单例模式的实现的介绍就到这里了,但是,关于单例模式的介绍还没完。

懒汉式单例

 

何为懒汉式单例呢,可以这样理解,单例模式呢,其在整个应用程序的生命周期中只存在一个实例,

 

懒汉式呢,就是这个单例类的这个唯一实例是在第一次使用 GetInstance()时实例化的,

 

如果您不调用 GetInstance()的话,这个实例是不会存在的,即为 null

 

形象点说呢,就是你不去动它的话,它自己是不会实例化的,所以可以称之为懒汉。

 

看下面的 GetInstance()方法就明白了:

 

饿汉式单例

其由于肚子饿了,所以到处找东西吃,人也变得主动了很多,所以根本就不需要别人来催他实例化单例类的为一实例,

 

其自己就会主动实例化单例类的这个唯一类。

 

在 C# 中,可以用特殊的方式实现饿汉式单例,即使用静态初始化来完成饿汉式单例模式

 

下面就来看一看

饿汉式单例类

 

namespace Singleton

{

    public sealed class Singleton

    {

        private static readonly Singleton singleton = new Singleton();

 

        private Singleton()

        {

        }

 

        public static Singleton GetInstance()

        {

            return singleton;

        }

    }

}