单例模式
提供了一种创建对象的最佳方式。
-
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
优点:在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例,避免对资源的多重占用
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
创建方式
按照进阶的方式一步步来,但是重点都是,私有的构造函数。
创建一个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(); }
结果
因为静态类在第一次访问它时才会加载,并且只会加载一次,所以线程安全,而且更加简洁。