单例模式

提供了一种创建对象的最佳方式。

  • 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

 

意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决:一个全局使用的类频繁地创建与销毁。

何时使用:当您想控制实例数目,节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:构造函数是私有的。

 

优点:在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例,避免对资源的多重占用

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

 

创建方式

按照进阶的方式一步步来,但是重点都是,私有的构造函数

 创建一个SingleTon类,最简单的单例模式,饿汉式

public class SingleTon
    {
        private SingleTon() {
            Console.WriteLine("ThreadID:" + Thread.CurrentThread.ManagedThreadId + "---实例化对象");
        }

        private static SingleTon singleTon = new SingleTon();

        public static SingleTon GetInstance()
        {
            return singleTon;
        }
    }

 

获取类的实例

 static void Main(string[] args)
        {
            SingleTon singleTon1 =  SingleTon.GetInstance();
            SingleTon singleTon2 = SingleTon.GetInstance();
            SingleTon singleTon3 = SingleTon.GetInstance();

            Console.ReadLine();
        }

执行结果,明明调用了3次,为什么只输出1句话?因为对象已经创建了,不需要再创建,所以构造函数里的代码不会被重复调用。

 

 


上例的问题在于,一上来就实例化了SingleTon的对象,可能会浪费空间,而我们通常是希望,使用的时候才实例化,因此可以使用懒汉式,修改成这样:

public class SingleTon
    {
        private SingleTon() {
            Console.WriteLine("ThreadID:" + Thread.CurrentThread.ManagedThreadId + "---实例化对象");
        }

        private static SingleTon singleTon = null;

        public static SingleTon GetInstance()
        {
            if (singleTon == null)
            {
                singleTon = new SingleTon();
            }
            return singleTon;
        }
    }

对象初始化为null,调用GetInstance方发时判断是否为null,是则实例化。

 


 

上例在多线程的情况下,并不好用,它可能同时判断singleTon == null,导致重复实例化对象,示例

static void Main(string[] args)
        {
            //SingleTon singleTon1 =  SingleTon.GetInstance();
            //SingleTon singleTon2 = SingleTon.GetInstance();
            //SingleTon singleTon3 = SingleTon.GetInstance();

            for (int i = 0; i < 5; i++)
            {
                Task.Run(() => SingleTon.GetInstance());
            }
            Console.ReadLine();
        }

结果

 

 这种情况下,通过加锁来解决。

    public class SingleTon
    {
        private SingleTon() {
            Console.WriteLine("ThreadID:" + Thread.CurrentThread.ManagedThreadId + "---实例化对象");
        }

        private static SingleTon singleTon = null;

        //锁对象
        private static object singleTonLock = new object();

        public static SingleTon GetInstance()
        {
            lock (singleTonLock)
            {
                if (singleTon == null)
                {
                    singleTon = new SingleTon();
                }
            }
            return singleTon;
        }
    }

结果

 

 


但是这种写法也有点问题,就是我们第一次已经成功创建对象了,后续的操作不需要再去加锁,毕竟加锁会损耗性能,因此采用双if锁,DCL

        public static SingleTon GetInstance()
        {
            if (singleTon == null)
            {
                lock (singleTonLock)
                {
                    if (singleTon == null)
                    {
                        singleTon = new SingleTon();
                    }
                }
            }
            return singleTon;
        }

在加锁前判断是否实例化对象,避免了不必要的损耗,结果和上例一样。

 


 

还有一种更简洁的方式,静态内部类

public class SingleTon
    {
        private SingleTon() {
            Console.WriteLine("ThreadID:" + Thread.CurrentThread.ManagedThreadId + "---实例化对象");
        }

        //静态内部类
        private static class SingleTonInstance {
            public static SingleTon instance = new SingleTon();
        }

        public static SingleTon GetSingleTon()
        {
            return SingleTonInstance.instance;
        }
    }

调用

        static void Main(string[] args)
        {

            for (int i = 0; i < 5; i++)
            {
                Task.Run(() => SingleTon.GetSingleTon());
            }

            Console.ReadLine();
        }

结果

 

 因为静态类在第一次访问它时才会加载,并且只会加载一次,所以线程安全,而且更加简洁。

 

posted @ 2019-08-26 23:30  luytest  阅读(127)  评论(0编辑  收藏  举报