本文为转载,原文:C#实现设计模式 —— 单例模式
介绍
作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。
特点
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。
单例模式的实现
实现单例模式有两种方案,饿汉式和懒汉式。
饿汉式
public class EagerSingleton
{
private EagerSingleton() { }
private static EagerSingleton instance = new EagerSingleton();
public static EagerSingleton GetInstance()
{
return instance;
}
public void Print()
{
Console.WriteLine("this is eager singleton");
}
}
在这个类被加载时,静态变量instance会被初始化,此时类的私有构造子会被调用。这时候,单例类的唯一实例就被创建出来了。
饿汉式其实是一种比较形象的称谓。既然饿,那么在创建对象实例的时候就比较着急,饿了嘛,于是在装载类的时候就创建对象实例。
饿汉式是典型的空间换时间,当类装载的时候就会创建类的实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断,节省了运行时间。
懒汉式
public class LazySingleton
{
private LazySingleton() { }
private static LazySingleton _instance;
public static LazySingleton Instance
{
get
{
if (_instance == null)
{
_instance = new LazySingleton();
}
return _instance;
}
}
public void Print()
{
Console.WriteLine("this is lazy singleton");
}
}
上面的单例模式的实现在单线程下确实是完美的,然而在多线程的情况下会得到多个Singleton实例,因为在两个线程同时运行时,此时两个线程判断(_instance ==null)这个条件时都返回真,此时两个线程就都会创建Singleton的实例,这样就违背了我们单例模式初衷了,既然上面的实现会运行多个线程执行,那我们对于多线程的解决方案自然就是使new LazySingleton方法在同一时间只运行一个线程运行就好了,也就是我们线程同步的问题了(对于线程同步大家也可以参考我线程同步的文章),具体的解决多线程的代码如下:
public class LazySingleton
{
private LazySingleton() { }
private static LazySingleton _instance;
private static readonly object locker = new object();
public static LazySingleton Instance
{
get
{
lock (locker)
{
if (_instance == null)
{
_instance = new LazySingleton();
}
}
return _instance;
}
}
public void Print()
{
Console.WriteLine("this is lazy singleton");
}
}
上面这种解决方案确实可以解决多线程的问题,但是上面代码对于每个线程都会对线程辅助对象locker加锁之后再判断实例是否存在,对于这个操作完全没有必要的,因为当第一个线程创建了该类的实例之后,后面的线程此时只需要直接判断(_instance==null)为假,此时完全没必要对线程辅助对象加锁之后再去判断,所以上面的实现方式增加了额外的开销,损失了性能,为了改进上面实现方式的缺陷,我们只需要在lock语句前面加一句(_instance==null)的判断就可以避免锁所增加的额外开销,这种实现方式我们就叫它 “双重锁定”,下面具体看看实现代码的:
public class LazySingleton
{
private LazySingleton() { }
private static LazySingleton _instance;
private static readonly object locker = new object();
public static LazySingleton Instance
{
get
{
if (_instance == null)
{
lock (locker)
{
if (_instance == null)
{
_instance = new LazySingleton();
}
}
}
return _instance;
}
}
public void Print()
{
Console.WriteLine("this is lazy singleton");
}
}
饿汉式和懒汉式区别
从名字上来说,饿汉和懒汉,
饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,
而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。
另外从以下两点再区分以下这两种方式:
-
线程安全:
饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,
懒汉式本身是非线程安全的,为了实现线程安全需要加锁。 -
资源加载和性能:
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,
而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
测试代码
static void Main(string[] args)
{
bool flag = true;
while (flag)
{
Console.WriteLine("=================设计模式测试=================");
Console.WriteLine("====1 - 简单工厂模式==========================");
Console.WriteLine("====2 - 工厂方法模式==========================");
Console.WriteLine("====3 - 抽象工厂模式==========================");
Console.WriteLine("====4 - 单例模式==============================");
Console.WriteLine("====0 - 退出==================================");
Console.WriteLine("==============================================");
Console.Write("请输入编号:");
string num = Console.ReadLine();
switch (num)
{
case "1":
SimpleFactory();
break;
case "2":
FactoryMethod();
break;
case "3":
AbstractFactory();
break;
case "4":
Singleton();
break;
case "0":
flag = false;
break;
default:
break;
}
Console.WriteLine("\r\n");
}
}
static void Singleton()
{
EagerSingleton.GetInstance().Print();
LazySingleton.Instance.Print();
}
运行结果
完
转载请注明出处:
C#实现设计模式 —— 单例模式
网友评论