c#设计模式(1)——单例模式
一、引言
最近看了一些单例模式的介绍,感觉可以总结一下,形成自己的笔记。部分内容选自https://www.cnblogs.com/zhili/p/SingletonPatterm.html,感觉写得很好,我只是再加点东西
二、介绍
单例模式(Singleton Pattern)是最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
三、多种单例模式的创建方式
1.饿汉式创建单例模式
说明:饿汉就是类一旦加载,就把单例初始化完成,保证CreateSingleton的时候,单例是已经存在的了。饿汉式创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成。
/// <summary> /// 饿汉式写法 /// </summary> public class SingletonEH { /** *是否 Lazy 初始化:否 *是否多线程安全:是 *实现难度:易 *描述:这种方式比较常用,但容易产生垃圾对象。 *优点:没有加锁,执行效率会提高。 *缺点:类加载时就初始化,浪费内存。 *它基于 classloder 机制避免了多线程的同步问题, * 不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种, * 在单例模式中大多数都是调用 CreateSingleton 方法, * 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载, * 这时候初始化 instance 显然没有达到 lazy loading 的效果。 */ private static SingletonEH instance = new SingletonEH(); private SingletonEH() { Console.WriteLine($"{typeof(SingletonEH).Name}被构造。。。"); } public static SingletonEH CreateSingleton() { return instance; } }
2.懒汉式创建单例模式
说明:而懒汉比较懒,只有当调用CreateSingleton的时候,才回去初始化这个单例。懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
/// <summary> /// 懒汉式 /// </summary> public class SingletonLH { /** *是否 Lazy 初始化:否 *是否多线程安全:是 *实现难度:易 *描述:这种方式比较常用,但容易产生垃圾对象。 *优点:没有加锁,执行效率会提高。 */ private static SingletonLH instance = null; private SingletonLH() { Console.WriteLine($"{typeof(SingletonLH).Name}被构造。。。"); } public static SingletonLH CreateInstance() { if(instance == null) { return new SingletonLH(); } return instance; } }
3.上面的单例模式的实现在单线程下确实是完美的,然而在多线程的情况下会得到多个Singleton实例,因为在两个线程同时运行CreateInstance方法时,此时两个线程判断(instance ==null)这个条件时都返回真,此时两个线程就都会创建Singleton的实例,这样就违背了我们单例模式初衷了,既然上面的实现会运行多个线程执行,那我们对于多线程的解决方案自然就是使CreateInstance方法在同一时间只运行一个线程运行就好了,也就是我们线程同步的问题了。
public class SingletonLH { /** *是否 Lazy 初始化:否 *是否多线程安全:是 *实现难度:易 *描述:这种方式比较常用,但容易产生垃圾对象。 *优点:没有加锁,执行效率会提高。 *缺点:类加载时就初始化,浪费内存。 *它基于 classloder 机制避免了多线程的同步问题, * 不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种, * 在单例模式中大多数都是调用 CreateInstance 方法, * 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载, * 这时候初始化 instance 显然没有达到 lazy loading 的效果。 */ private static SingletonLH instance = null; private static readonly object locker = new object(); private SingletonLH() { Console.WriteLine($"{typeof(SingletonLH).Name}被构造。。。"); } public static SingletonLH CreateInstance() { lock (locker) { if(instance == null) { return new SingletonLH(); } } return instance; } }
上面代码对于每个线程都会对线程辅助对象locker加锁之后再判断实例是否存在,对于这个操作完全没有必要的,因为当第一个线程创建了该类的实例之后,后面的线程此时只需要直接判断(instance==null)为假,此时完全没必要对线程辅助对象加锁之后再去判断,所以上面的实现方式增加了额外的开销,损失了性能,为了改进上面实现方式的缺陷,我们只需要在lock语句前面加一句(instance==null)的判断就可以避免锁所增加的额外开销,这种实现方式我们就叫它 “双重锁定”,
public class SingletonLH { private static SingletonLH instance = null; private static readonly object locker = new object(); private SingletonLH() { Console.WriteLine($"{typeof(SingletonLH).Name}被构造。。。"); } public static SingletonLH CreateInstance() { if(instance == null) { lock (locker) { if(instance == null) { return new SingletonLH(); } } } return instance; } }
4. 泛型封装单例模式,利于重复利用
a.新建一个类 BaseSingleton类
public class BaseSingleton<T> where T: new() { protected static T _instance; private static readonly object locker = new object(); public static T getInstance() { if(_instance == null) { lock (locker) { if(_instance == null) { _instance = new T(); } } } return _instance; } }
b.需要用到的类继承调用
public class Singleton: BaseSingleton<Singleton> { public int Id { get; set; } public string Name { get; set; } public static void Test() { Console.WriteLine("this is Test..."); } }