代码改变世界

我的设计模式之旅(2)——单件模式Singleton

2006-07-10 11:33  努力学习的小熊  阅读(944)  评论(0编辑  收藏  举报

单件模式,在某些情况下保证一个类的对象实例,在系统的运行中仅存在一个,并提供一个该实例的全局访问点。这就意味着一个特定的对象存在后,只允许存在一个,而且它承担了一些职责,而其他的对象都要依赖这些职责。例如在一个公司中的老总,或软件中心的部门经理,这两种角色在一个公司中都是独一无二的,他们都有自己独特的职责。而隶属于他们的各个部门经理和公司员工都依赖于他们的领导。

这时我们要保证这个类的对象不会被创建2次或2次以上,根据C#语言的特点,如果一个类没有构造函数,编译器会为他产生一个缺省的构造函数(而且是一个无参的共有的实例构造器),所以在代码中我们首先要解决这个问题,就是我们自己编写构造函数,并将它作为一个私有的构造函数,保证他不会被使用者来创建。

private Singleton() {}

第二个我们要解决的问题就是提供一个该实例的全局访问点,既然是全局访问点,我们提供的属性或者方法应该就是public的。这里其实作为属性或者方法或者直接实例化我们的对象都是可以的,只要能满足我们的这种单件需求就可以。

(1)

提供属性:

        public static Singleton Instance

        {

            get

            {

                if(instance == null)

                {

                    instance = new Singleton();

                }

                return instance;

            }

        }

我们在访问的时候:

            Singleton s1 = Singleton.Instance;

(2)

提供一个访问的方法:

        public static Singleton Instance()

        {

            if(instance == null)

            {

                instance = new Singleton();

            }

            return instance;

        }

我们在访问的时候:

            Singleton s1 = Singleton.Instance();

(3)

直接实例化:

        private static Singleton instance = new Singleton();

但是不推荐使用这种方法,原因是(1)和(2)使用的是Lazy initialization方法,惰性初始化,这样的情况是当我们需要的时候在初始化这个实例,因为如果使用(3)的方法有可能还未满足我们创建初始化对象所需要的参数、条件等,所以推荐使用(1)或(2)的惰性初始化的方法来提供这个单件实例。

 

这样我就构造出了一个单线程下的单件模式类

    public class Singleton

    {

        private static Singleton instance;

 

        private Singleton() {}

 

        public static Singleton Instance()

        {

            if(instance == null)

            {

                instance = new Singleton();

            }

            return instance;

        }

    }

然后我们来测试一下:

    class MainApp

    {

        static void Main()

        {

            // Constructor is protected -- cannot use new

            Singleton s1 = Singleton.Instance();

            Singleton s2 = Singleton.Instance();

            if (s1 == s2)

            {

                Console.WriteLine("Objects are the same instance");

            }

            Console.WriteLine(Object.ReferenceEquals(s1,s2) == true);

            // Wait for user

            Console.Read();

        }

    }

得到的测试结果如下:

证明了我们在单线程下实现了这种单件模式的设计。

 

以下是收看李建忠老师记录下的几点注意:

Singleton模式中的实例构造器可以设置为protected以允许子类派生。

Singleton模式一般不要支持ICloneable接口,因为这可能会导致多个对象实例,与Singleton模式的初衷违背。

Singleton模式一般不要支持序列化,因为这也有可能导致多个对象实例,同样与Singleton模式的初衷违背。

Singleton模式只考虑到了对象创建的管理,没有考虑对象销毁的管理。就支持垃圾回收的平台和对象的开销来讲,我们一般没有必要对其销毁进行特殊的管理。

不能应对多线程环境:在多线程环境下,使用Singleton模式仍然有可能得到Singleton类的多个实例对象。

 

下面来看多线程的例子,其中因为上面已经说到,多线程下可能在每个线程都创建了一个实例,导致这个我们仅需要一个对象的实例在两个或两个以上的线程同时请求时,由于都判断了对象为null,所以都通过了判断均执行了创建的方法,所以导致对象被创建了多次,这样就不满足我们的要求了。

 

这时我们可以通过加锁的办法来解决这种冲突

    public class Singleton

    {

        private static volatile Singleton instance = null;

 

        private static object lockHelper = new Object();

 

        private Singleton() {}

 

        public static Singleton Instance()

        {

            if(instance == null)

            {

                lock(lockHelper)

                {

                    if(instance == null)

                    {

                        instance = new Singleton();

                    }

                }

            }

            return instance;

        }

    }

这里我们使用了对于创建单件对象的双检查,用加锁来避免多线程的访问,以这种思想来解决了单件模式在多线程中应用的问题。

在声明单件对象的时候使用了volatile关键字,因为在编译器编译的时候可能对于代码的顺序有微调,而这个volatile就会保证不对这段代码作微调,所以对于多线程情况下保证不会由于编译时代码顺序出现微调而导致创建多个对象。

 

利用C#的特性来实现单件模式

    sealed class Singleton

    {

        public static readonly Singleton Instance = new Singleton();

        private Singleton() {}

    }

这其中使用到了一个叫内联初始化的概念声明的同时对其进行初始化),静态的只读字段等同于以下的代码

    sealed class Singleton

    {

        public static readonly Singleton Instance;

 

        static Singleton()

        {

            Instance = new Singleton();

        }

 

        private Singleton() {}

    }

这里的做法不像前面一样将对象放在一个方法或只读属性里对其进行初始化,这里是放在一个静态的构造器里对它进行初始化。首先静态构造器的执行时间只在静态字段初始化之间初始化,在内联初始化的时候会将初始化工作放到相应的静态构造器中进行。而且静态构造器也可以保证在多线程环境下只有一个线程执行进该构造器,不可能有多个线程去执行静态构造器,在.NET机制中就自动为这个构造器加了一个锁,来避免多线程的冲突。

 

最后是一个网上的例子,服务器均衡负载的单件模式的实现

using System;

using System.Collections;

using System.Threading;

 

namespace DesignPatterns

{

    /// <summary>

    /// SingletonSample 的摘要说明。

    /// </summary>

    class MainApp

    {

        [STAThread]

        static void Main()

        {

            LoadBalancer b1 = LoadBalancer.GetLoadBalancer();

            LoadBalancer b2 = LoadBalancer.GetLoadBalancer();

            LoadBalancer b3 = LoadBalancer.GetLoadBalancer();

            LoadBalancer b4 = LoadBalancer.GetLoadBalancer();

 

            // Same instance?

            if (b1 == b2 && b2 == b3 && b3 == b4)

            {

                Console.WriteLine("Same instance\n");

            }

 

            // All are the same instance -- use b1 arbitrarily

            // Load balance 15 server requests

            for (int i = 0; i < 15; i++)

            {

                Console.WriteLine(b1.Server);

            }

 

            // Wait for user

            Console.Read();

        }

    }

 

    // "Singleton"

    class LoadBalancer

    {

        private static LoadBalancer instance;

        private ArrayList servers = new ArrayList();

 

        private Random random = new Random();

 

        // Lock synchronization object

        private static object syncLock = new object();

 

        // Constructor (protected)

        protected LoadBalancer()

        {

            // List of available servers

            servers.Add("ServerI");

            servers.Add("ServerII");

            servers.Add("ServerIII");

            servers.Add("ServerIV");

            servers.Add("ServerV");

        }

 

        public static LoadBalancer GetLoadBalancer()

        {

            // Support multithreaded applications through

            // 'Double checked locking' pattern which (once

            // the instance exists) avoids locking each

            // time the method is invoked

            if (instance == null)

            {

                lock (syncLock)

                {

                    if (instance == null)

                    {

                        instance = new LoadBalancer();

                    }

                }

            }

 

            return instance;

        }

 

        // Simple, but effective random load balancer

        public string Server

        {

            get

            {

                int r = random.Next(servers.Count);

                return servers[r].ToString();

            }

        }

    }

}