单例模式的五个实现方法比较 via C#

来源:互联网 发布:动态主题软件 编辑:程序博客网 时间:2024/05/17 06:51
单例模式:就是指一个类里面只一个实例,并提供一个全局的访问点。在C#里,单例模式的实现方便有很多种。个人的见解是,由于整个类只有一个实例对象,因此,必须做到这人实例对象只能由类它本身来管理,即由该类来创建与销毁这个实例。

以下是五种创建单例的做法。虽然它们表面上都实现了由类本身管理这个实例,但是却未必都是正确的做法。

方法一:
 public class Singleton
    {
        private static Singleton _instance;

        private Singleton()
        {
        }

        public static Singleton GetInstance()
        {
            if (_instance == null)
            {
                _instance = new Singleton();
            }
            return _instance;
        }
    }
    说明:以上的实现在单线程的情况上基本正确,但是却几乎不可能在多线程的情况下正确运行。其原因是“红色”部分的几行语句并不是一个原子操作,仅从C#而言,就已经是由多个语句组成的;如果再变成IL代码或者是底层的汇编代码,那将是更多条语句。因此,这里无法实现在“红色”部分代码的原子操作。所以在多线程的访问下有可能会失败。

方法二:
基于”方法一“,为了确保判断实例与创建实例的“原子操作”,那最简单的做法就是加锁。
 public class Singleton
    {
        private static Singleton _instance;

        private static object obj = new object();
        private Singleton()
        {
        }

        public static Singleton GetInstance()
        {

            lock (obj)
            {
                if (_instance == null)
                {
                    _instance = new Singleton();
                }
            }
            return _instance;
        }
    }
    说明:看起来”方法二“已经修复了”方法一“的bug了。但是,难道每一次调用:“GetInstance()"方法是我都不得不加锁吗?要知道,锁可不是说加就加的,是有代价的。

方法三:
继续改进”方法二“吧。我们的目标是不要每一次请求”GetInstance()“就加锁,其实,加锁只有在_instance未实例话时需要,所以:
 public class Singleton
    {
        private static Singleton _instance;

        private static object obj = new object();
        private Singleton()
        {
        }

        public static Singleton GetInstance()
        {
            if (_instance == null)
            {
                lock (obj)
                {
                    if (_instance == null)
                    {
                        _instance = new Singleton();
                    }
                }
            }
            return _instance;
        }
    }
    说明:经过了”方法三“的处理之后,”红色“部分的代码只会完整地执行一次,而以后的请求将不会再加锁了。因此,加锁的次数将会大大减少,成功的加锁只会有一次。

方法四:
    为什么一定要加锁呢,不如:
 public class Singleton
    {
        private static Singleton _instance = new Singleton();

        private Singleton()
        {
        }

        public static Singleton GetInstance()
        {
            return _instance;
        }
    }
    说明:充分利用类的静态构造函数吧。静态字段是由类的静态构造函数负责实例化。在多线程的情况下,CLR确保了每个AppDomain中只对静态构造函数执行一次。在多线程的环境下,CLR为每个想要调用静态构造函数的方法提供一个互拆线程同步锁。因此,只有一个线程可以获得这个锁,并执行静态构造函数对字段进行实例化。其他的线程处于阻塞的状态。等第一个线程释放锁时,其他线程被唤醒,但是发现构造器的代码已经执行过了,因此,不再继续执行,而是直接返回。

方法五:
    但是,这样一来就不是”按需分配“了,如果这个Singleton实例的初始化需要大量的时间和资源呢,而且又无法保证这个Singleton实例一定会被进程使用。这样不就是浪费资源吗?
  public class Singleton
    {
        private Singleton()
        {
        }

        public static Singleton GetInstance()
        {
            return Nest.Instance;   
        }

        class Nest
        {
            private static Singleton _instance = new Singleton();

            private Nest()
            {
            }

            public static Singleton Instance
            {
                get { return _instance; }
            }
        }
    }

说明:
    和”方法四“类似,我们可以保证这个单例模式在多线程的情况下是可正确运行的,因为Nest 类只有Singleton可访问,并且Singleton类是不能被继承的,因为它的构造方法为private。而且我们也做到了”按需分配“。
    如果Singleton.GetInstance()方法没有被调用,也就是没有发起对Nest的静态构造函数的调用,因此,_instance实例不会被创建。
    只有在调用Singleton.GetInstance()方法被调用之后,才会调用Nest类的静态构造函数(而且这个静态构造函数的调用 对多线程也是需要加互斥同步锁的),所以可以确保只对_instance实例创建一次。


各位看官有何想法?


原创粉丝点击