C#实现单例模式的⼏种⽅法总结
⽬录
介绍
Version 1 - ⾮线程安全
Version 2 - 简单的线程安全
Version 4 - 不完全懒汉式,但不加锁的线程安全
Version 5 - 完全懒汉实例化
Version 6 - 使⽤.NET 4 Lazy<T> type 特性
总结
介绍
单例模式是软件⼯程学中最富盛名的设计模式之⼀。从本质上看,单例模式只允许被其⾃⾝实例化⼀次,
且向外部提供了⼀个访问该实例的接⼝。通常来说,单例对象进⾏实例化时⼀般不带参数,因为如果不同的实例化请求传递的参数不同的话会导致问题的产⽣。(若多个请求都是传递的同样的参数的话,⼯⼚模式更应该被考虑)
C#中实现单例有很多种⽅法,本⽂将按顺序介绍⾮线程安全、完全懒汉式、线程安全和低/⾼性能集中版本。
在所有的实现版本中,都有以下⼏个共同点:
唯⼀的、私有的且⽆参的构造函数,这样不允许外部类进⾏实例化;
类是密封的,尽管这不是强制的,但是严格来讲从上⼀点来看密封类能有助于JIT的优化;
⼀个静态变量应该指向类的唯⼀实例;
⼀个公共的静态变量⽤于获得这个类的唯⼀实例(如果需要,应该创建它);
需要注意的是,本⽂中所有的例⼦中都是⽤⼀个 public static Instance的变量来访问单例类实例,要将其转换成公共函数是很容易的,但是这样并不会带来效率和线程安全上的提升。
Version 1 - ⾮线程安全
/// <summary>
/// Bad code!Do not use!
/// </summary>
public sealed class Singleton
{
private static Singleton instance = null;
private Singleton() { }
public static Singleton Instance
{
get
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
该版本在多线程下是不安全的,会创建多个实例,请不要在⽣产环境中使⽤!
因为如果两个线程同时运⾏到if(instance==null)判断时,就会创建两个实例,这是违背单例模式的初衷的。实际上在后⾯那个线程进⾏判断是已经⽣成了⼀个实例,但是对于不同的线程来说除⾮进⾏了线程间的通信,否则它是不知道的。
Version 2 - 简单的线程安全
public sealed class Singleton2
{
private static Singleton2 instance = null;
private static readonly object obj = new object();
private Singleton2() { }
public Singleton2 Instance
{
get
{
lock (obj)
{
if (instance == null)
{
instance = new Singleton2();
}
return instance;
}
}
}
}
该版本是线程安全的。通过对⼀个过线程共享的对象进⾏加锁操作,保证了在同⼀时刻只有⼀个线程
在执⾏lock{}⾥的代码。当第⼀个线程在进⾏instance判断或创建时,后续线程必须等待直到前⼀线程执⾏完毕,因此保证了只有第⼀个线程能够创建instance实例。
但不幸的是,因为每次对instance的请求都会进⾏lock操作,其性能是不佳的。
需要注意的是,这⾥使⽤了⼀个private static object变量进⾏锁定,这是因为当如果对⼀个外部类可以访问的对象进⾏锁定时会导致性能低下甚⾄死锁。因此通常来说为了保证线程安全,进⾏加锁的对象应该是private的。
Version 3 - Double-check locking的线程安全
/// <summary>
/// Bad code ! Do not use!
/// </summary>
public sealed class Singleton3
{
private static Singleton3 instance = null;
private static object obj = new object();
private Singleton3() { }
public static Singleton3 Instance
{
get
{
if (instance == null)
{
lock (obj)
{
if (instance == null)
{
instance = new Singleton3();
}
}
}
return instance;
}
}
}
该版本中试图去避免每次访问都进⾏加锁操作并实现线程安全。然后,这段代码对Java不起作⽤,因J
ava的内存模型不能保证在构造函数⼀定在其他对象引⽤instance之前完成。还有重要的⼀点,它不如后⾯的实现⽅式。
Version 4 - 不完全懒汉式,但不加锁的线程安全
public sealed class Singleton4
{
private static readonly Singleton4 instance = new Singleton4();
/// <summary>
/// 显式的静态构造函数⽤来告诉C#编译器在其内容实例化之前不要标记其类型
/// </summary>
static Singleton4() { }
private Singleton4() { }
public static Singleton4 Instance
{
get
{
return instance;
}
}
}
这个版本是的实现⾮常的简单,但是却⼜是线程安全的。C#的静态构造函数只有在当其类的实例被创建或者有静态成员被引⽤时执⾏,在整个应⽤程序域中只会被执⾏⼀次。使⽤当前⽅式明显⽐前⾯版本中进⾏额外的判断要快。
当然这个版本也存在⼀些瑕疵:
不是真正意义上的懒汉模式(需要的时候才创建实例),若单例类还存在其他静态成员,当其他类第⼀次
引⽤这些成员时便会创建该instance。下个版本实现会修正这个问题;
单例模式的几种实现方式只有.NET中才具有beforefieldinit特性,即懒汉式实现。且在.Net 1.1以前的编译器不⽀持,不过这个现在来看问题不⼤;
所有版本中,只有这⾥将instance设置成了readonly,这不仅保证了代码的⾼校且显得⼗分短⼩。
Version 5 - 完全懒汉实例化
public sealed class Singleton5
{
private Singleton5() { }
public static Singleton5 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 Singleton5 instance = new Singleton5();
}
}
该版本看起来稍微复杂难懂,其实只是在写法上实现了上⼀版本的瑕疵,通过内嵌类的⽅式先实现了只有在真正应⽤Instance 时才进⾏实例化。其性能表现与上⼀版本⽆异。
Version 6 - 使⽤.NET 4 Lazy<T> type 特性
public sealed class Singleton6
{
private static readonly Lazy<Singleton6> lazy =
new Lazy<Singleton6>(()=> new Singleton6());
public static Singleton6 Instance { get { return lazy.Value; } }
private Singleton6() { }
}
如果你使⽤的是.NET 4或其以上版本,可以使⽤System.Lazy<T> type来实现完全懒汉式。其代码看起来也很简洁且性能表现也很好。
性能 VS 懒汉式
⼀般情况下,我们并不需要实现完全懒汉式,除⾮你的构造初始化执⾏了某些费时的⼯作。因此⼀般的,我们使⽤显式的静态构造函数就能够适⽤。
本⽂翻译⾃Implementing the Singleton Pattern in C#, 作者在⽂中做了⼀些循环测试,具体的读者可直接阅读原⽂。
Exception
有时候在进⾏构造函数初始化时可能会抛出异常,但这对整个应⽤程序来说不应该是致命的,所以可能的情况下,你应该⾃⼰处理这种异常情况。
总结
上述提供的⼏种实现⽅法中,⼀般情况下提倡使⽤Version 4,除⾮遇到有时早于单列类实例化时就引⽤了其他静态成员。这种情况下,Version 2⼀旦被考虑,虽然它看起来会因加锁耗时,但是其实运⾏起来并没有你想的那么慢,关键是你很容易写对它。
显然Version 1你永远都不应该考虑,Version 3在与Version 5的对⽐下也是不在考虑范围之内的。
到此这篇关于C#实现单例模式的⽂章就介绍到这了,更多相关C#实现单例模式内容请搜索以前的⽂章或继续浏览下⾯的相关⽂章希望⼤家以后多多⽀持!

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。