正文
一、什么是单例模式
1.生活中的单例
一个男人只能有一个媳妇「正常情况」,一个人只能有一张嘴,通常一个公司只有一个 CEO ,一个狼群中只有一个狼王等等
2.程序中的单例
一句话,就是保证一个类仅有一个实例即可「new 一次」,其实好多人都不把单例当作成一个设计模式,只是当作是一个工具类而已,因为它的确很简单,并且当你面视的时候面视官问你设计模式的时候估计都会说:可以说说你了解的设计模式吗「单例除外」。虽然很简单,但是我们还是要掌握和了解它,并且要深层次的了解它。
单例模式的定义
单例单例就是单一的实例,官方定义: 保证一个类只有一个实例,并提供一个全局访问点。
单例的应用
- 网站的计数器
- 应用配置
- 多线程池一般也采用单例去设计
- 数据库配置,数据库连接池
- window窗体不能同时出现两个
- 其它等等
1.单例模式的几种实现方式
1.Lazy模式(懒汉式)线程不安全
懒汉式单例模式,是在我需要的时候才去初始化实例,也就是说在类加载的时候,静态成员变量是 null 的,只有需要它的时候才去初始化实例,所以懒汉式可以延时加载
特点
- 1、线程不安全
- 2、延时初始化类,在我需要的时候「也就调用 GetInstance」的时候才去初始化
优缺点
- 优点:延时初始化类,省资源,不想用的时候就不会浪费内存
- 缺点:线程不安全,多线程操作就会有问题
演示代码
/// <summary>
/// Singleton mode implementation
/// </summary>
public class Singleton
{
//define a static variable to hold an instance of the class
private static Singleton instance;
//define a private constructor,so that the outside can't cteate an instanse of the class
private Singleton() { }
/// <summary>
/// define public methods to provide a global access point, and you can also define public properties to provide global access points
/// </summary>
public static Singleton GetInstance()
{
// create if the instance of the class does not exist, otherwise return directly
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
解析如下:
1)首先,该Singleton的构造函数必须是私有的,以保证客户程序不会通过new()操作产生一个实例,达到实现单例的目的;
2)因为静态变量的生命周期跟整个应用程序的生命周期是一样的,所以可以定义一个私有的静态全局变量instance来保存该类的唯一实例;
3)必须提供一个全局函数访问获得该实例,并且在该函数提供控制实例数量的功能,即通过if语句判断instance是否已被实例化,如果没有则可以同new()创建一个实例;否则,直接向客户返回一个实例。
上面单例模式的实现在单线程下确实是完美的,但没有考虑线程并发获取实例问题,在多线程的情况下会得到多个Singleton实例,因为两个线程同时运行GetInstance方法时,此时两个线程判断(instance==null)这个条件都成立,此时两个线程就都会创建Singleton的实例,违反了单例规则。既然上面的实现会运行多个线程执行,那我们对于多线程的解决方案自然就是使GetInstance方法在同一时间只让一个线程运行就好了。因此,需对上面代码修改,看下面。
2、Lazy模式(懒汉式)线程安全
懒汉式线程安全比懒汉式线程不安全多了一个线程安全
特点
- 1、线程安全
- 2、延时初始化类,在我需要的时候「也就调用 GetInstance」的时候才去初始化
优缺点
- 优点:延时初始化类,省资源,不想用的时候就不会浪费内存,并且线程安全
- 缺点:虽然线程安全,但是加了锁对性能影响非常大「相当于排队获取资源,没有拿到锁子就干等」
代码演示
/// <summary>
/// Singleton mode implementation
/// </summary>
public class Singleton
{
//define a static variable to hold an instance of the class
private static Singleton instance;
// define an identity to ensure thread synchronization
private static readonly object locker = new object();
//define a private constructor,so that the outside can't cteate an instanse of the class
private Singleton() { }
/// <summary>
/// define public methods to provide a global access point, and you can also define public properties to provide global access points
/// </summary>
public static Singleton GetInstance()
{
// create if the instance of the class does not exist, otherwise return directly
//When the first thread runs here, the locker object is "locked" at this time.
//When the second thread runs the method,
//it first detects that the locker object is in the "locked" state,
//and the thread will hang waiting for the first thread to unlock.
//After the lock statement finishes running (that is, after the thread finishes running), it will "unlock" the object.
//Double lock only needs one judgment.
if (instance == null)
{
lock (locker)
{
// create if the instance of the class does not exist, otherwise return directly
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
}
上述代码使用了双重锁方式较好地解决了多线程下的单例模式实现。先看内层的if语句块,使用这个语句块时,先进行加锁操作,保证只有一个线程可以访问该语句块,进而保证只创建了一个实例。再看外层的if语句块,这使得每个线程欲获取实例时不必每次都得加锁,因为只有实例为空时(即需要创建一个实例),才需加锁创建,若果已存在一个实例,就直接返回该实例,节省了性能开销。
到这里,有的同学可能会有疑问,lock语句内层的if(instance==null)感觉是多余的,其实一开始一看我也觉得是多余的,仔细一想,这句代码是不能省的,两个线程同时运行lock外层的if (instance == null),都成立,第一条线程加锁实例化一个对象,解锁后,如果不加判断,第二条线程直接实例化一个对象,这就不是单例了。
3、饿汉模式
饿汉式单例模式如其名,是一个饿货,类的实例在类加载的时候就初始化出来「把这一过程当作一个汉堡,也就是说必须要把汉堡提前准备好,饿货就知道吃」, 这种模式的特点是自己主动实例。
特点
- 1、是线程安全的
- 2、类不是延时加载「直接是类加载的时候就初始化」
优缺点
- 优点:没有加锁,执行效率非常高「其实是以空间来换时间」
- 缺点:在类加载的时候就会初始化,浪费内存「你知道我要不要使用这个实例吗,你就给我初始化,太任性了」
演示代码
public sealed class Singleton
{
private static readonly Singleton instance = new Singleton();
private Singleton() { }
public static Singleton GetInstance()
{
return instance;
}
}
上面使用的readonly关键可以跟static一起使用,用于指定该常量是类别级的,它的初始化交由静态构造函数实现,并可以在运行时编译。在这种模式下,无需自己解决线程安全性问题,CLR会给我们解决。由此可以看到这个类被加载时,会自动实例化这个类,而不用在第一次调用GetInstance()后才实例化出唯一的单例对象。
Reference:
1.C#设计模式(1)——单例模式
2.人人都会设计模式:单例模式–SingleTon
3.C#设计模式学习笔记-单例模式
4.在C#中实现 Singleton