单例模式
定义
单例模式,也叫单子/单态模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。wiki
用例
1. Windows 的任务管理器
2. 网站的计数器
3. 应用程序的日志应用
4. Web应用的配置对象的读取
5. 数据库连接池
6. 局域网中的共享打印机
总结:
- 资源共享的情况下,避免由于资源操作时导致的性能损耗
- 多线程(进程)环境中避免 IO 资源争用
- 控制资源的情况下,方便资源之间的互相通信。如线程池等
思路
实现单例模式的思路是:一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。wiki
实现
-
第一个版本
// 单例模式的实现 v1 public class Singleton { // 定义一个静态变量来保存类的实例 private static Singleton uniqueInstance; // 私有构造方法 private Singleton () { } //公有静态方法提供全局访问点(也可以定义公有属性提供全局访问点) public static Singleton GetInstance () { if (null == uniqueInstance) { uniqueInstance = new Singleton(); } return uniqueInstance; } }
请思考多线程环境下这段示例代码的表现
单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)。
-
第二个版本(互斥锁)
// 单例模式的实现 v2 public class Singleton { // 定义一个静态变量来保存类的实例 private static Singleton uniqueInstance; // 定义互斥对象 private static readonly object locker = new object(); // 私有构造方法 private Singleton () { } //公有静态方法提供全局访问点(也可以定义公有属性提供全局访问点) public static Singleton GetInstance () { /* 此时假设我们有两个线程同时访问该方法,当第一个线程运行到这里时,会对locker对象执行“加锁”操作 第二个线程过来一看,锁上了啊,好吧,那只好等等了,于是第二个线程就挂起,直到第一个线程对locker对象执行解锁操作 */ lock (locker) { if (null == uniqueInstance) { uniqueInstance = new Singleton(); } } return uniqueInstance; } }
请思考这段代码的不足之处
-
第三个版本
前面第二个版本中,有缺陷的地方在于每个线程都对locker进行了lock操作,那么,我们先检测一下 uniqueInstance, 只有在 uniqueInstance 为 null 的时候再进行 lock 操作:
// 单例模式的实现 v3 public class Singleton { // 定义一个静态变量来保存类的实例 private static Singleton uniqueInstance; // 定义互斥对象 private static readonly object locker = new object(); // 私有构造方法 private Singleton () { } //公有静态方法提供全局访问点(也可以定义公有属性提供全局访问点) public static Singleton GetInstance () { /* 先判断是否为 null, 再进行加锁操作 */ if (null == uniqueInstance) { lock (locker) { if (null == uniqueInstance) { uniqueInstance = new Singleton(); } } } return uniqueInstance; } }
-
第四个版本(饿汉模式)
这种模式的特点是自己主动实例化public sealed class Singleton { private static readonly Singleton instance = new Singleton(); private Singleton () { } public static Singleton GetInstance() { return instance; } }
readonly关键可以跟static一起使用,用于指定该常量是类级别的,初始化由静态构造函数实现,可以在运行时编译。在这种模式下,不需要自己解决线程安全问题,CLR会替我们解决。当该类被加载时,会自动实例化这个类,而不是在第一次调用 GetInstance 才实例化出唯一的单例对象。
-
第五个版本(Lazy模式)
public sealed class Singleton { private static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton()); public static Singleton Instance { get { return lazy.Value; } } private Singleton() { } }
实际应用
讲完了理论和示例,来看一下在实际项目中单例模式的应用,这里以日志操作对象为例:
public sealed class Logger
{
private static readonly string LogFile = "c:/log.txt";
private static readonly Logger logger = new Logger();
private Logger() { }
public static Logger GetLogger()
{
return logger;
}
public void Append(string logline)
{
var line = $"{DateTime.Now.ToString()} - {logline}";
System.IO.File.AppendAllText(LogFile, line);
}
}
总结
一般在实际应用中,采用饿汉式比较多。还有其他单例模式的实现方式,这里没有一一列举说明。
文中资料来源:
维基百科
Implementing the Singleton Pattern in C#
C#设计模式(1)——单例模式