单例模式是开发人员在开发中比较常用的一种设计模式,实现方式也很简单,大致原理:在我们程序开发中始终保持一个类的唯一单例,因此我们必须严格控制它的实例创建,一般的设计方式会在类中定义一个私有的成员变量instance以及一个静态的共有方法getinstance(),他负责检测和实例化自己并存储在成员变量中
1、单实现方式如下
public sealed class Singleton //关键字sealed表示该类不能被继承
{
private static Singleton _instance = null;
//构造函数私有保证不会在外部被调用
private Singleton()
{
}
/// <summary>
/// Gets the instance.
/// </summary>
public static Singleton Instance
{
//如果_instance 非空则初始化
get { return _instance ?? (_instance = new Singleton()); }
}
}
上面实现只适用于单线程环境,如果是多线程有可能会创建多个实例
2、线程安全实现方式
public sealed class Singleton
{
private static Singleton _instance = null;
private static readonly object SynObject = new object();
Singleton()
{
}
/// <summary>
/// Gets the instance.
/// </summary>
public static Singleton Instance
{
get
{
// Syn operation.
lock (SynObject)
{
return _instance ?? (_instance = new Singleton());
}
}
}
}
以上方式的实现方式是线程安全的,首先我们创建了一个静态只读的进程辅助对象,由于lock是确保当一个线程位于代码的临界区时,另一个线程不能进入临界区(同步操作)。如果其他线程试图进入锁定的代码,则它将一直等待,直到该对象被释放。从而确保在多线程下不会创建多个对象实例了。只是这种实现方式要进行同步操作,这将是影响系统性能的瓶颈和增加了额外的开销。
3、Double-Checked Locking
public sealed class Singleton
{
private static Singleton _instance = null;
// Creates an syn object.
private static readonly object SynObject = new object();
Singleton()
{
}
public static Singleton Instance
{
get
{
// Double-Checked Locking
if (null == _instance)
{
lock (SynObject)
{
if (null == _instance)
{
_instance = new Singleton();
}
}
}
return _instance;
}
}
}
前面讲到的线程安全的实现方式的问题是要进行同步操作,那么我们是否可以降低通过操作的次数呢?其实我们只需在同步操作之前,添加判断该实例是否为null就可以降低通过操作的次数了,这样是经典的Double-Checked Locking方法。
4、静态初始化
public sealed class Singleton
{
private static readonly Singleton _instance = new Singleton();
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Singleton()
{
}
/// <summary>
/// Prevents a default instance of the
/// <see cref="Singleton"/> class from being created.
/// </summary>
private Singleton()
{
}
/// <summary>
/// Gets the instance.
/// </summary>
public static Singleton Instance
{
get
{
return _instance;
}
}
}
以上方式实现比之前介绍的方式都要简单,但它确实是多线程环境下,C#实现的Singleton的一种方式。由于这种静态初始化的方式是在自己的字段被引用时才会实例化。
让我们通过IL代码来分析静态初始化。首先这里没有beforefieldinit的修饰符,由于我们添加了静态构造函数当静态字段被引用时才进行初始化,因此即便很多线程试图引用_instance,也需要等静态构造函数执行完并把静态成员_instance实例化之后可以使用。
5、延迟初始化
/// <summary>
/// Delaies initialization.
/// </summary>
public sealed class Singleton
{
private Singleton()
{
}
/// <summary>
/// Gets the instance.
/// </summary>
public static Singleton Instance { get { return Nested._instance; } }
private class Nested
{
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Nested()
{
}
internal static readonly Singleton _instance = new Singleton();
}
}
//或者泛型延迟
public class Singleton<T> where T : new()
{
public static T Instance
{
get
{
return SingletonCreator.instance;
}
}
class SingletonCreator
{
internal static readonly T instance = new T();
}
}
这里我们把初始化工作放到Nested类中的一个静态成员来完成,这样就实现了延迟初始化
6、Lazy<T> type
/// <summary>
/// .NET 4's Lazy<T> type
/// </summary>
public sealed class Singleton
{
private static readonly Lazy<Singleton> lazy =
new Lazy<Singleton>(() => new Singleton());
public static Singleton Instance { get { return lazy.Value; } }
private Singleton()
{
}
}
这种方式简单并且性能良好,而且还提供检查是否已经创建实例的属性IsValueCreated。
单例模式的优点:
单例模式(Singleton)会控制其实例对象的数量,从而确保访问对象的唯一性。
- 实例控制:单例模式防止其它对象对自己的实例化,确保所有的对象都访问一个实例。
- 伸缩性:因为由类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
单例模式的缺点:
- 系统开销。虽然这个系统开销看起来很小,但是每次引用这个类实例的时候都要进行实例是否存在的检查。这个问题可以通过静态实例来解决。
- 开发混淆。当使用一个单例模式的对象的时候(特别是定义在类库中的),开发人员必须要记住不能使用new关键字来实例化对象。因为开发者看不到在类库中的源代码,所以当他们发现不能实例化一个类的时候会很惊讶。
- 对象生命周期。单例模式没有提出对象的销毁。在提供内存管理的开发语言(比如,基于.NetFramework的语言)中,只有单例模式对象自己才能将对象实例销毁,因为只有它拥有对实例的引用。在各种开发语言中,比如C++,其它类可以销毁对象实例,但是这么做将导致单例类内部的指针指向不明。
单例适用性
使用Singleton模式有一个必要条件:在一个系统要求一个类只有一个实例时才应当使用单例模式。反之,如果一个类可以有几个实例共存,就不要使用单例模式。
不要使用单例模式存取全局变量。这违背了单例模式的用意,最好放到对应类的静态成员中。
不要将数据库连接做成单例,因为一个系统可能会与数据库有多个连接,并且在有连接池的情况下,应当尽可能及时释放连接。Singleton模式由于使用静态成员存储类实例,所以可能会造成资源无法及时释放,带来问题。