二十三种设计模式[5] - 单件模式(Singleton Pattern)
前言
单件又名单例,五种创建模式中的一种。《设计模式 - 可复用的面向对象软件》一书中将之描述为“ 保证一个类仅有一个实例,并提供一个访问它的全局访问点 ”。
单件模式一般在一个类只需要一个实例时使用。比如为了保证原型管理器只存在一个实例,可以将原型管理器设计成一个单件(二十三种设计模式[4] - 原型(Prototype))。
结构
需要角色如下:
- 单件(Singleton):定义一个操作,允许客户访问它的唯一实例;
示例
单件模式的实现方式有很多种,主要为预加载和懒加载。预加载(又称饿汉式)是指当前对象在它所属类被加载时而加载。懒加载(又称懒汉式)是指当前对象在真正使用时才去创建,不用时不创建。下面示例中,将使用预加载和懒加载去实现单件,如下:
-
预加载
public class SingletonA { private SingletonA() { Console.WriteLine("开始初始化SingletonA"); } private static SingletonA _instance = new SingletonA(); public static SingletonA Instance { get { return _instance; } } public void ShowName() { Console.WriteLine("SingletonA"); } } class Program { static void Main(string[] args) { for (int i = 0; i < 5; i++) { //异步 Task.Factory.StartNew(() => { SingletonA.Instance.ShowName(); }); } Console.ReadKey(); } }
在这种实现方式下,将SingletonA类的构造器私有化保证了只能在该类的内部创建该类的实例,同时利用CLR对类的加载机制保证了_instance只会被创建一次并随着类的加载而记载,从而达到单件效果的同时保证了线程的安全性。
-
懒加载
public class SingletonB { private SingletonB() { Console.WriteLine("开始初始化SingletonB"); } private static SingletonB _instance = null; public static SingletonB Instance { get { if(_instance == null) { _instance = new SingletonB(); } return _instance; } } public void ShowName() { Console.WriteLine("SingletonB"); } } class Program { static void Main(string[] args) { for (int i = 0; i < 5; i++) { //异步 Task.Factory.StartNew(() => { SingletonB.Instance.ShowName(); }); } Console.ReadKey(); } }
从运行结果来看是不能保证线程安全的。在异步时可能会有多个线程同时进行_instance == null的判断并对_instance进行初始化操作。在这种情况下可采用互斥锁实现线程同步,来保证_instance的初始化操作同时只能被一个线程执行。实现如下:
public class SingletonB { private SingletonB() { Console.WriteLine("开始初始化SingletonB"); } private static object _sysLock = new object(); private static SingletonB _instance = null; public static SingletonB Instance { get { if(_instance == null) { lock (_sysLock) { if(_instance == null) { _instance = new SingletonB(); } } } return _instance; } } public void ShowName() { Console.WriteLine("SingletonB"); } } class Program { static void Main(string[] args) { for (int i = 0; i < 5; i++) { //异步 Task.Factory.StartNew(() => { SingletonB.Instance.ShowName(); }); } Console.ReadKey(); } }
因为互斥锁在多线程的情况下会降低效率,所以只在需要对_instance进行初始化时上锁。上锁后对_instance的二次校验是为了防止在当前线程等待期间,上一线程已对_instance进行了初始化操作导致当前线程对它的的二次初始化。这种方式被称为双重校验锁(DCL, double-check locking)。
除了DCL,还可采用内部类的方式实现对单件的懒加载。如下:
public class SingletonC { private SingletonC() { Console.WriteLine("开始初始化SingletonC"); } private class SingletonCHelper { public static SingletonC _instance = new SingletonC(); } public static SingletonC Instance { get { return SingletonCHelper._instance; } } public void ShowName() { Console.WriteLine("SingletonC"); } } class Program { static void Main(string[] args) { for (int i = 0; i < 5; i++) { //异步 Task.Factory.StartNew(() => { SingletonC.Instance.ShowName(); }); } Console.ReadKey(); } }
CLR对类的加载机制保证了在加载SingletonC时并不会加载它的子模块(即内部类SingletonCHelper)。所以内部类SingletonCHelper只会在第一次使用Instance属性时被加载,并在该类内部对SingletonC进行预加载,达到懒加载的同时保证了线程安全。
总结
单件模式使得一个类在系统内存中只存在一个实例,节省了系统资源。在对于一些需要频繁创建、销毁的对象使用单件模式时可以提高系统性能。
单件模式的实现方式有很多种,但无论哪种方试,最核心的目的是要保证这个类在系统内存中只存在一个实例。
本文示例中使用的开发语言为C#,所以在示例中所实现的单件并不能避免通过序列化反序列化操作来创建新的实例,而在Java中可通过枚举实现单件来避免序列化反序列化操作对于单件模式造成的破坏。
以上,就是我对单件模式的理解,希望对你有所帮助。
示例源码:https://gitee.com/wxingChen/DesignPatternsPractice
系列汇总:https://www.cnblogs.com/wxingchen/p/10031592.html
本文著作权归本人所有,如需转载请标明本文链接(https://www.cnblogs.com/wxingchen/p/10078578.html)