Head First设计模式——单例模式

单例模式是全部设计模式中最简单的模式,也是咱们日常常常用到的,单例模式一般被咱们应用于线程池、缓存操做、队列操做等等。设计模式

单例模式旨在建立一个类的实例,建立一个类的实例咱们用全局静态变量或者约定也能办到单例的做用,为何咱们要用单例模式?缓存

接下来咱们就从如何造成单例模式,单例模式建立的过程来说解。多线程

一、单例如何造成

咱们日常建立一个对象须要new对象,假若有一个对象ObjectClass咱们实例化它。函数

new ObjectClass()

若是另一个类要使用ObjectClass则能够再经过new来建立另一个实例化,若是这个类是public 则咱们能够在使用的时候屡次实例化对象。性能

那咱们怎么保证类不被其余类实例化,利用private关键字咱们能够采用私有构造函数来阻止外部实例化该类。测试

public class ObjectClass
{
   private ObjectClass()
    {
    }    
}

这样一来咱们没法实例化ObjectClass则咱们就没法使用它。那咱们要怎么实例化呢?优化

因为私有构造方法咱们只能在内部访问,因此咱们能够用一个内部方法实例化ObjectClass,为了外部可以访问这个方法咱们将这个方法设置成static。spa

这样作了以后确保返回对象始终是第一次建立的对象,咱们用一个私有静态对象来存储实例化的对象,若是对象没建立咱们则当即建立,若是已经建立就返回已经建立的对象。线程

    public class ObjectClass
    {
        private static ObjectClass singleton;
        private ObjectClass()
        {
        }

        public static ObjectClass GetSingletone()
        {
            if (singleton == null)
            {
                singleton = new ObjectClass();
            }
            return singleton;
        }
    }

到这里咱们的单例模式就造成了,单例模式定义:设计

单例模式:确保一个类只有一个实例,并提供一个全局访问点。

二、多线程致使单例模式问题

启用多线程测试单例返回对象

    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 10; i++)
            {
                TestSingleton();
            }
            Console.ReadKey();
        }

        public static void TestSingleton()
        {
            Task.Factory.StartNew(new Action(() =>
            {
                var hc = ObjectClass.GetSingletone().GetHashCode();
                Console.WriteLine(hc);
            }));
        }
    }

  

如图中作的测试同样,我启了10个线程得到单例对象而后打印对象的HashCode。测试发现有HashCode不一致的状况,证实单例返回的对象并非只有一个。

由于多线程运行的时候可能会同时进入if (singleton == null)的判断,若是此时singleton变量还没被实例化则可能有多个线程进入到实例化代码,以致于返回的实例化对象不是同一个。

三、解决多线程单例问题

因为多线程致使if检查变量问题,则争对检查问题咱们能够有两类解决办法:

①"急切"建立实例,不用延迟实例化作法

急切实例化就是在静态初始化器中建立对象,这样就保证了程序运行阶段单例对象已经建立好,去除if判断。

    public class ObjectClass
    {
        private static ObjectClass singleton=new ObjectClass();
        private ObjectClass()
        {
        }

        public static  ObjectClass GetSingletone()
        {
            return singleton;
        }
    }

加锁

为了让建立对象只能有一个线程操做,则咱们对建立对象代码进行加锁处理,再次改造GetSingletone方法。

    public class ObjectClass
    {
        private static ObjectClass singleton = new ObjectClass();
        private static object lockObj = new object();
        private ObjectClass()
        {
        }

        public static ObjectClass GetSingletone()
        {
            lock (lockObj)
            {
                if (singleton == null)
                {
                    singleton = new ObjectClass();
                }
            }
            return singleton;
        }
    }

 加锁对性能有必定的损耗,若是你的系统对性能要求比较高,咱们对于加锁的处理还有一种优化方式:双重检查加锁

     public static ObjectClass GetSingletone()
        {
            if (singleton == null)
            {
                lock (lockObj)
                {
                    if (singleton == null)
                    {
                        singleton = new ObjectClass();
                    }
                } 
            }
            return singleton;
        }

使用双重检查加锁,则多线程在运行的时候若是已经建立了单例对象后就不会再进入到lock代码段以此减小锁带来的性能损耗。

而后咱们再来测试一波,启用50个线程,能够看到输出的HashCode是一致的。

四、总结

回到咱们开始讲的为何不用全局变量或者约定来解决单例问题,由于对于咱们开发来讲虽然有约定可是咱们不能保证每一个人都按照约定或者滥用全局变量形成问题。

而使用单例模式能进行更好的自我约定和管理,固然咱们也有可能会滥用单例模式,这就须要对它能解决什么问题如何使用深刻理解。

设计模式并非要生搬硬套,而是在须要的时候符合的场景进行合理使用。

虽然单例模式比较简单,但经过分析咱们看到问题也很多,要更好的使用须要咱们更好的分析,也但愿这篇博文对你有些帮助。

相关文章
相关标签/搜索