设计模式——单例模式
1.前言
很多时候,我们需要为某个类型创建独一无二的对象。比如系统配置文件、工具类、线程池、缓存、系统日志等,此时单例模式应运而生。
单例模式: 确保一个类只有一个实例,并提供一个全局访问点
举例1
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 5 namespace 单例模式 6 { 7 public class Program 8 { 9 static void Main(string[] args) 10 { 11 Singleton s1 = Singleton.GetInstance(); 12 Singleton s2 = Singleton.GetInstance(); 13 14 if (s1 == s2) 15 { 16 Console.WriteLine("Objects are the same instance"); 17 } 18 19 Console.Read(); 20 } 21 } 22 23 public class Singleton 24 { 25 private static readonly Singleton instance = new Singleton(); 26 private Singleton() { } 27 public static Singleton GetInstance() 28 { 29 return instance; 30 } 31 } 32 }
从输出结果可知,两次实例化得到的是同一个对象。
2.单例模式的特点
单从实现来看,单例模式有以下特点
- 构造函数访问权限为private,即不允许外界通过调用构造函数实例化;
- 提供一个静态方法作为该类实例的唯一全局访问点
- 任何时候只返回一个实例
单例模式UML类图如下
3.懒汉模式
如果在多线程下,能不能保证GetInstance()方法只创建一个实例呢?很显然是不行的。在C#中可以使用lock语句来保证对临界区进行完全访问(所谓的临界区是指在多线程访问共享资源时,使用独占式访问的代码段来对共享资源实施保护的一种手段)。接下来我们看看多线程下的单例模式
3.1举例2(多线程时的单例)
1 namespace 单例模式 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 Singleton s1 = Singleton.GetInstance(); 8 Singleton s2 = Singleton.GetInstance(); 9 10 if (s1 == s2) 11 { 12 Console.WriteLine("Objects are the same instance"); 13 } 14 15 Console.Read(); 16 } 17 } 18 19 public class Singleton 20 { 21 private static Singleton instance; 22 private static readonly object syncRoot = new object(); 23 private Singleton() 24 { 25 } 26 27 public static Singleton GetInstance() 28 { 29 lock (syncRoot) 30 { 31 if (instance == null) 32 { 33 instance = new Singleton(); 34 } 35 } 36 return instance; 37 } 38 } 39 }
这个例子能不能保证多线程安全呢?能。最先进入的那个线程会创建对象实例,保证类只实例化一次。但这样每次都lock的话,一旦多次调用时,第一个调用的会进入lock,而其他的线程则需要等待第一个结束才能依次调用,后面的依次调用,等待......所以会导致性能损耗。lock加锁就好比漏斗一样,每次只能样一个线程通过
3.2举例3(多线程下多重锁定的单例)
为了解决例子2中的多次锁定问题,我们进行稍微的改动,同时解决了线程安全和性能的问题。(若没有性能方面的顾虑,这个方法就是杀鸡用了牛刀)
1 namespace 单例模式 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 Singleton s1 = Singleton.GetInstance(); 8 Singleton s2 = Singleton.GetInstance(); 9 10 if (s1 == s2) 11 { 12 Console.WriteLine("Objects are the same instance"); 13 } 14 15 Console.Read(); 16 } 17 } 18 19 public class Singleton 20 { 21 private static Singleton instance; 22 private static readonly object syncRoot = new object(); 23 private Singleton() 24 { 25 } 26 27 public static Singleton GetInstance() 28 { 29 if (instance == null) 30 { 31 lock (syncRoot) 32 { 33 if (instance == null) 34 { 35 instance = new Singleton(); 36 } 37 } 38 } 39 return instance; 40 } 41 } 42 }
4.饿汉模式
通过静态初始化的方式在类加载时就进行实例化操作,相比于懒汉模式,不存在线程安全的问题,如下
1 namespace 单例模式 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 Singleton s1 = Singleton.GetInstance(); 8 Singleton s2 = Singleton.GetInstance(); 9 10 if (s1 == s2) 11 { 12 Console.WriteLine("Objects are the same instance"); 13 } 14 15 Console.Read(); 16 } 17 } 18 19 public sealed class Singleton 20 { 21 private static readonly Singleton instance = new Singleton(); 22 private Singleton() 23 { 24 } 25 26 public static Singleton GetInstance() 27 { 28 return instance; 29 } 30 } 31 }
5.饿汉模式 VS 懒汉模式
饿汉模式:即利用静态初始化的方式,类一旦加载就立即实例化。其特点是加载类时比较慢,但运行时比较快,并且线程安全。
懒汉模式:即等到类第一次被引用时,才会对其实例化操作(延迟实例化 )。其特点是加载时比较快,但运行时比较慢,存在线程不安全的问题(需要双重锁定来保证多线程访问的安全性)。
今天是坚持晨读的第21天,也把这段时间的知识作下总结,写得不好的地方,欢迎讨论指出。
浙公网安备 33010602011771号