单例模式是结构最简单地设计模式,在它地核心结构中只包含一个被称为单例类地特殊类。
单例模式概述
对于一个软件系统中地某些类而言,只有一个实例很重要。例如:一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID生成器。在Windows操作系统中只能打开一个任务管理器窗口。如果不使用机制对窗口对象进行唯一化,势必会弹出多个窗口。如果这些窗口显示地内容完全移值,则是重复对象,浪费内存资源;如果这些窗口显示内容不一致,则意味着某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此,有时确保系统中某个对象的唯一性非常重要。
定义
单例模式:确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。
单例模式有3个要点:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
单例模式的结构与实现
单例模式UML单例模式的实现
单例模式的目的是保证一个类有且仅有一个实例,并提供一个访问它的全局访问点。单例模式包含的角色只有一个,就是单例类-Singleton。单例类拥有一个私有构造函数,确保用户无法通过new关键字直接实例化它。
单例模式的实现代码如下:
public class Singleton
{
private static Singleton instance = null; // 静态私有成员变量
// 私有构造函数
private Singleton() { }
// 静态公有工厂方法,返回唯一实例
public static Singleton GetInstance() {
if(instance == null)
{
instance = new Singleton();
}
return instance;
}
}
测试:
class Program
{
static void Main(string[] args)
{
// 单例测试
var s1 = Singleton.GetInstance();
var s2 = Singleton.GetInstance();
// 判断两个对象是否相同
if (s1 == s2)
{
Console.WriteLine("两个对象实例相同。");
}
else
{
Console.WriteLine("两个对象实例不同");
}
Console.ReadLine();
}
}
测试结果
在单例模式的实现过程中,用户需要注意以下3点:
- 单例类构造函数的可见性为private
- 提供一个类型为自身的静态私有成员变量
- 提供一个公有的静态工厂方法
饿汉式单例与懒汉式单例
饿汉式单例
饿汉式单例类(Eager Singleton)是实现起来最简单的单例类,结构如下:
实例代码如下:
public class EagerSingleton
{
private static EagerSingleton instance = new EagerSingleton();
private EagerSingleton() { }
public static EagerSingleton GetInstance()
{
return instance;
}
}
测试结果
当类被加载时,静态变量instance会被初始化,此时类的私有构造函数会被调用,单例类的唯一实例将被创建。
懒汉式单例类与双重检查锁定
饿汉式单例类在第一次被引用时将自己实例化,懒汉式单例类加载时不会自己实例化。
在懒汉式单例类中,不是在定义静态变量时实例化单例类,而是在第一次调用静态工厂方法时实例化单例类。
但是懒汉式单例存在一个很严重的问题:如果在高并发、多线程环境下实现懒汉式单例类,在某一个时刻可能会有多个线程需要使用单例对象,即会有多个线程同时调用GetInstace()方法,可能会造成创建多个实例对象,这将违背单例模式的设计意图。为了阻止生成多个单例对象,需要使用C#语言中的lock关键字,lock关键字锁定的代码片段称为临界区,可以确保当一个线程位于代码的临界区时,另一个线程不能进入临界区。如果其他线程试图进入锁定的代码,则将一直等待,直到该对象被释放为止。
代码实例:
public class LazySingleton
{
private static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton GetInstance()
{
if (instance == null)
{
instance = new LazySingleton();
}
return instance;
}
}
测试结果
加了双重锁定代码示例:
public class LazySingleton
{
private static LazySingleton instance = null;
// 程序运行时创建一个静态只读的辅助对象
private static readonly object syncRoot = new object();
private LazySingleton() { }
public static LazySingleton GetInstance()
{
// 第一重判断, 先判断实例是否存在,不存在再加锁处理
if (instance == null)
{
// 加锁的程序再某一时刻只允许一个线程访问
lock (syncRoot)
{
// 第二重判断
if (instance == null)
{
instance = new LazySingleton();
}
}
}
return instance;
}
}
测试结果
为了更好的对单例对象的创建进行控制,此处使用了一种被称为双重检查锁定(Double-Check Locking)的双重判断机制。再双重检查锁定中,当实例不存在且同时又两个线程调用GetInstance()方法时,它们都可以通过第一重“instance == null” 判断,并且由于lock锁定机制,只有一个线程进入lock中执行创建代码,另一个线程处于排队等待状态,必须等待第一个线程执行完毕才可以进入lock锁定的代码,如果此时不进行第二重“instance == null”判断,第二个线程并不知道实例已经创建,将继续创建新的实例,还是会产生多个单例对象,违背了单例模式的设计思想,因此需要进行双重检查。
饿汉式单例类于懒汉式单例类比较
- 饿汉式单例类在加载时就将自己实例化,它的优点在于无须考虑多个线程同时访问的问题,可以确保实例的唯一性;
- 从调用的速度和反应时间角度来讲,由于单例对象一开始就得以创建,因此要优于懒汉式单例。但是无论在系统运行时是否需要使用该单例对象,由于在类加载时该对象就需要创建,因此从资源利用率角度来讲,饿汉式单例不及懒汉式单例,而且在系统加载时由于需要创建饿汉式单例对象,加载时间可能会比较长。
- 懒汉式单例类在第一个使用时创建,无须一直占用系统资源,实现了延迟加载,但是必须处理多个线程同时访问的问题,特别是当单例类作为资源控制器,在实例化时必然会涉及资源初始化,而资源初始化很有可能耗费大量时间,这意味着出现多线程同时首次引用此类的概率变大,需要通过双重检查锁定等机制进行控制,这将导致系统性能受到一定影响。
单例模式的优缺点与适用环境
单例模式作为一种明确、结构简单、理解容易的设计模式,在软件开发中使用频率相当高,在很多应用软件和框架中都得以广泛应用。
单例模式的优点
- 提供了唯一实例的受控访问
- 由于系统内存中只存在一个对象,节约了系统资源
- 单例模式允许可变数目的实例。基于单例模式的扩展,指定个数的实例对象,既节省系统资源,又解决了由于单例对象共享过多有损性能的问题。
单例模式的缺点
- 没有抽象层,扩展困难
- 职责过重,在一定程度上违背了单一职责原则
- 现在很多面向对象语言的运行都提供了自动垃圾回收技术,因此,实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,在下次利用时又将重新实例化,这将导致共享的单例对象状态丢失。
单例模式使用环境
- 系统只需要一个实例对象。
- 客户调用类的单个实例只允许使用一个公共访问点。
如果觉得文章写得还行,请点个赞。如果想与我进一步交流,可以关注我的公众号或者加我的微信。
个人微信公众号_DotNet微说.jpg
网友评论