单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。
关键点:
1)一个类只有一个实例
2)它必须自行创建这个实例
3)它必须自行向整个系统提供这个实例
一、应用场景
一般数据底层封装的connection db连接、Read File、WCF、Mongodb等非托管资源,因为每一次操作目标对象的时候都需要new一个对象,然后在和目标数据建立连接,因为在这个过程中需要耗费很多资源更主要的是耗费时间,所有能否有一个办法能够保存new出来的对象这样就不需要每次调用的时候都要new一个对象,这不我们可以通过将要实例化的对象进行Static这样就相当于在类中缓存了一个静态对象,这样就不需要我们每次调用的时候在实例化了,
二、代码走起来
首先看下面单例模式:
public class SingleClass
{
//饿汉单例模式
//在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快
private static SingleClass singleClass=new SingleClass();//静态私有成员,已初始化
/// <summary>
/// 构造函数
/// </summary>
private SingleClass()
{
Thread.Sleep(5000);
Console.WriteLine("实例化");
}
/// <summary>
/// 获取单例对象
/// </summary>
/// <returns></returns>
public static SingleClass GetInstance()
{
//静态,不用同步(类加载时已初始化,不会有多线程的问题)
return singleClass;
}
/// <summary>
/// 方法
/// </summary>
private static void ShowMessage()
{
//Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("ShowMessage");
}
private static void Main(string[] args)
{
var instance = SingleClass.GetInstance();
Console.ReadLine();
}
}
运行结果是:
上面该模式称作为懒汉模式因为在类初始化的时候默认的就初始化静态实例对象singleClass会导致初始化加载的速度会稍微慢,但是在获取对象的时候会很快。但是懒汉模式存在一个缺点就是当调用该类中的其他静态方法的时候,该懒汉单例模式就会初始化静态对象,具体看如下代码:
public class SingleClass
{
//饿汉单例模式
//在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快
private static readonly SingleClass singleClass=new SingleClass();//静态私有成员,已初始化
/// <summary>
/// 构造函数
/// </summary>
private SingleClass()
{
Thread.Sleep(5000);
Console.WriteLine("实例化");
}
/// <summary>
/// 获取单例对象
/// </summary>
/// <returns></returns>
public static SingleClass GetInstance()
{
//静态,不用同步(类加载时已初始化,不会有多线程的问题)
return new SingleClass();
}
/// <summary>
/// 方法
/// </summary>
private static void ShowMessage()
{
//Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("ShowMessage");
}
private static void Main(string[] args)
{
//var instance = SingleClass.GetInstance();
SingleClass.ShowMessage();
Console.ReadLine();
}
}
运行结果:
说道这里不得不说到和懒汉模式相对的就是饱汉模式,首先看下面例子:
public class SingleClass
{
//饱汉式单例模式
//不饿,在类加载时,不创建实例,因此类加载速度快,但运行时获取对象的速度慢
private static SingleClass singleClass = null;//静态私用成员,没有初始化
/// <summary>
/// 构造函数
/// </summary>
private SingleClass()
{
Thread.Sleep(5000);
Console.WriteLine("实例化");
}
/// <summary>
/// 获取单例对象
/// </summary>
/// <returns></returns>
public static SingleClass GetInstance()
{
if (singleClass == null)
{
singleClass = new SingleClass();
}
return singleClass;
}
/// <summary>
/// 方法
/// </summary>
private static void ShowMessage()
{
//Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("ShowMessage");
}
private static void Main(string[] args)
{
//var instance = SingleClass.GetInstance();
SingleClass.ShowMessage();
Console.ReadLine();
}
}
运行结果:
在调用ShowMessage方法时候不创建实例,这样就不需要显示多余提示;但是如果多个线程同时调用该静态方法中的实例方法的时候;当第一个线程还没有创建好该对象,那么第二个(或者更多)线程就会创建该实例,这样的本来想一个对象为整个系统服务;但是现在反倒没有达成该想法,具体看如下代码:
public class SingleClass
{
//饱汉式单例模式
//不饿,在类加载时,不创建实例,因此类加载速度快,但运行时获取对象的速度慢
private static SingleClass singleClass = null;//静态私用成员,没有初始化
/// <summary>
/// 构造函数
/// </summary>
private SingleClass()
{
Thread.Sleep(5000);
Console.WriteLine("实例化");
}
/// <summary>
/// 获取单例对象
/// </summary>
/// <returns></returns>
public static SingleClass GetInstance()
{
if (singleClass == null)
{
singleClass = new SingleClass();
}
return singleClass;
}
/// <summary>
/// 方法
/// </summary>
private static void ShowMessage()
{
Console.WriteLine("当前线程编号是:"+Thread.CurrentThread.ManagedThreadId);
//Console.WriteLine("ShowMessage");
}
private static void Main(string[] args)
{
//var instance = SingleClass.GetInstance();
//SingleClass.ShowMessage();
Parallel.For(1, 10, (i) =>
{
SingleClass.GetInstance();
ShowMessage();
});
Console.ReadLine();
}
}
运行结果:
本来想一个静态对象就能够解决所有问题但是在多线程中每个方法实例化一个静态对象,导致该单例模式不管用,那有何解决办法呢?
双检锁模式:
为了防止多个线程调用一个静态实例方法会产生多个静态实例,具体解决方法是要用到Lock锁,代码如下:
public class SingleClass
{
//饱汉式单例模式
//不饿,在类加载时,不创建实例,因此类加载速度快,但运行时获取对象的速度慢
private static SingleClass singleClass = null;//静态私用成员,没有初始化
//该对象用于Lock保证只能一个线程执行获取实例的代码中
private static object lockMe=new object() ;
/// <summary>
/// 构造函数
/// </summary>
private SingleClass()
{
Thread.Sleep(5000);
Console.WriteLine("实例化");
}
/// <summary>
/// 获取单例对象
/// </summary>
/// <returns></returns>
public static SingleClass GetInstance(int chreadId)
{
if (singleClass == null)
{
lock (lockMe)
{
singleClass = new SingleClass();
Console.WriteLine($"线程{chreadId}正在New,创建instance时间是{DateTime.Now}");
}
}
return singleClass;
}
/// <summary>
/// 方法
/// </summary>
private static void ShowMessage()
{
Console.WriteLine($"当前线程编号是:{Thread.CurrentThread.ManagedThreadId}" );
//Console.WriteLine("ShowMessage");
}
private static void Main(string[] args)
{
//var instance = SingleClass.GetInstance();
//SingleClass.ShowMessage();
Parallel.For(1, 10, (i) =>
{
SingleClass.GetInstance(i);
//ShowMessage();
});
Console.ReadLine();
}
}
运行结果:
这个就可以保证多个线程每次请求一个实例方法获取的是相同的实例对象
我们可以吧代码写的更加完整,完整代码如下:
public class SingleClass
{
//饱汉式单例模式
//不饿,在类加载时,不创建实例,因此类加载速度快,但运行时获取对象的速度慢
private static SingleClass singleClass = null;//静态私用成员,没有初始化
//该对象用于Lock保证只能一个线程执行获取实例的代码中
private static object lockMe=new object() ;
/// <summary>
/// 构造函数
/// </summary>
private SingleClass()
{
Thread.Sleep(5000);
Console.WriteLine("实例化");
}
/// <summary>
/// 获取单例对象
/// </summary>
/// <returns></returns>
public static SingleClass GetInstance(int chreadId)
{
//初次判断静态对象是否为空为了进入队里排队实例化对象
if (singleClass == null)
{
lock (lockMe)
{
//轮到当前线程实例化静态对象的时候;要判断静态对象是否已经实例化了
if (singleClass == null)
{
singleClass = new SingleClass();
Console.WriteLine($"线程{chreadId}正在New,创建instance时间是{DateTime.Now}");
}
else
{
Console.WriteLine($"我进入LockMe但是我发现别人已经实例化好了对象我可以直接就用了");
return singleClass;
}
}
}
return singleClass;
}
/// <summary>
/// 方法
/// </summary>
private static void ShowMessage()
{
Console.WriteLine($"当前线程编号是:{Thread.CurrentThread.ManagedThreadId}" );
//Console.WriteLine("ShowMessage");
}
private static void Main(string[] args)
{
//var instance = SingleClass.GetInstance();
//SingleClass.ShowMessage();
Parallel.For(1, 10, (i) =>
{
SingleClass.GetInstance(i);
//ShowMessage();
});
Console.ReadLine();
}
}
运行结果: