定义:
确保一个类只有一个实例,并提供一个全局访问点。
在C#里一个类的实例通常用new来实现,如果提供了公共的构造函数,则所有人都可以new一个实例出来,这就没法确保只有一个实例了,所以不能提供公共的构造函数,要实现单例模式,我们都得对类的构造函数进行处理。
思路:
(1)把无参构造函数和拷贝构造函数私有化;这避免外部通过new创建实例;
(2)定义一个类内的静态成员作为返回单例类对象;
(3)在类外初始化时,需要new一个对象全局访问点;
(4)把静态成员对象的权限设置为私有,然后提供一个静态成员函数或属性让外面能够获取这个静态成员对象。
特点:
单例类只有一个实例对象;
单例对象必须由单例类自行创建;
单例类对外提供一个访问该单例的全局访问点。
优点:
以保证内存里只有一个实例,减少了内存的开销,节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
避免对资源的多重占用;
设置全局访问点,可以优化和共享资源的访问。
缺点:
一般没有接口,扩展困难;
在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象;
功能代码设计不合理的话,容易违背单一职责原则。
注意:
线程安全,即如何确保在多线程中只new一个实例化对象(唯一的全局访问点)
知识点扩展:
多线程,类、静态成员属性的初始化过程。
缺点:
一般没有接口,扩展困难;
代码:
1、简单单例
namespace Pattern.Singleton.Single_1
{
public class Singleton
{
//定义一个静态变量保存唯一实例对象(保证instance在所有线程中同步)
private static Singleton? _Instance = null;
/// <summary>
/// 构造函数 (注意:构造函数必须是private的,防止类在外部被实例化 )
/// </summary>
private Singleton()
{
}
/// <summary>
/// 定义公有方法提供一个全局访问点
/// </summary>
public static Singleton GetInstance()
{
// 如果类的实例不存在则创建,否则直接返回
if (_Instance==null)
{
_Instance = new Singleton();
}
return _Instance;
}
/// <summary>
/// 定义公有属性来提供全局访问点
/// </summary>
public static Singleton Initance
{
get
{
if (_Instance == null)
{
_Instance = new Singleton();
}
return _Instance;
}
}
}
}
以上代码为:简单单例,在单线程下是完美的。
问题:多线程的情况下可能会得到多个Singleton实例,因为在两个线程同时运行GetInstance方法时,此时两个线程判断(_Instance==null)这个条件时都返回真,此时两个线程就都会创建。
解决:通过加锁的方式,确保GetInstance()方法或Initance属性,在一时间内只能在一个线程上执行,代码参见:加锁单例Single_2 或 饿汉式单例加锁单例Single_4。
2、加锁单例
namespace Pattern.Singleton.Single_2
{
public class Singleton
{
//定义一个静态变量保存唯一实例对象
private static Singleton? _Instance;
//定义一个标识确保线程同步(只是一个标识)
private static readonly object _Locker = new object();
/// <summary>
/// 构造函数 (注意:构造函数必须是private的,防止类在外部被实例化 )
/// </summary>
private Singleton()
{
}
/// <summary>
/// 定义公有方法提供一个全局访问点
/// </summary>
public static Singleton GetInstance()
{
// 当第一个线程运行到这里时,此时会对locker对象 "加锁",
// 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
// lock语句运行完之后(即线程运行完之后)会对该对象"解锁"
lock (_Locker)
{
if (_Instance == null)
{
_Instance = new Singleton();
}
}
return _Instance;
}
/// <summary>
/// 定义公有属性来提供全局访问点
/// </summary>
public static Singleton Initance
{
get
{
lock (_Locker)
{
if (_Instance == null)
{
_Instance = new Singleton();
}
}
return _Instance;
}
}
}
}
以上代码为:加锁单例,加锁单例确保在多线程中只存在唯一实例
问题:每个线程都会对线程辅助对象_Locker加锁之后再判断实例是否存在,这个操作是完没有必要的,因为_Locker锁为确保不重复创建,而不是为了访问。所以当_Instance!=null时,就没有必要加锁了。在这种情况下加锁的实现方式增加了额外的开销,损失了性能。
解决:在加锁前选判断_Instance!=null,不为空则直接返回唯一实例,为空则加锁创建唯一实例,这就是所说的“双重锁定”或“双检单例”,代码参见:双检单例Single_3。
3、双检单例
namespace Pattern.Singleton.Single_3
{
public class Singleton
{
//定义一个静态变量保存唯一实例对象
private static Singleton? _Instance;
//定义一个标识确保线程同步(只是一个标识)
private static readonly object _Locker = new object();
/// <summary>
/// 构造函数 (注意:构造函数必须是private的,防止类在外部被实例化 )
/// </summary>
private Singleton()
{
}
/// <summary>
/// 定义公有方法提供一个全局访问点
/// </summary>
public static Singleton GetInstance()
{
// 当第一个线程运行到这里时,此时会对locker对象 "加锁",
// 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
// lock语句运行完之后(即线程运行完之后)会对该对象"解锁"
if (_Instance == null)
{
lock (_Locker)
{
if (_Instance == null)
{
_Instance = new Singleton();
}
}
}
return _Instance;
}
/// <summary>
/// 定义公有属性来提供全局访问点
/// </summary>
public static Singleton Initance
{
get
{
if (_Instance == null)
{
lock (_Locker)
{
if (_Instance == null)
{
_Instance = new Singleton();
}
}
}
return _Instance;
}
}
}
}
以上代码为:双检单例,多线程下是完美的,单线程下也没有问题。加锁单例确保在多线程中只存在唯一实例。
4、饿汉式单例
namespace Pattern01.Singleton.Single_4
{
public class Singleton
{
private static Singleton _Instance = new Singleton();
private Singleton(){}
/*
上面两句等同于下面写法(知识点:静态变量的初始化)
private static Singleton _Instance;
private Singleton(){
_Instance = new Singleton();
}
*/
/// <summary>
/// 定义公有方法提供一个全局访问点
/// </summary>
public static Singleton GetInstance()
{
return _Instance;
}
/// <summary>
/// 定义公有属性来提供全局访问点
/// </summary>
public static Singleton Initance
{
get
{
return _Instance;
}
}
}
}
以上代码为:饿汉式单例,不存在线程问题。
问题:在类加载的期间,就已经将 instance 静态实例初始化好了,所以_Instance实例的创建是线程安全的。不过,这样的实现方式不支持延迟加载实例。
解决:双检单例Single_3。
注意:前三种单例模式全局访问点都是在使用时才会创建实例,也就是我们所说的懒汉模式。
代码下载地址:https://download.csdn.net/download/zhupt/87776525
留给大家思考的问题:
1、为什么我们要用单例模式?
2、单例模式与静态类有什么区别?