单件模式在C#中的实现--Implementing the Singleton Pattern in C#

来源:互联网 发布:手机源码怎么用 编辑:程序博客网 时间:2024/05/28 17:05


 

The singleton pattern is one of the best-known patterns in software engineering.

--单件模式是软件工程中流传比较久远的模式之一。

Essentially, a singleton is a class which only allows a single instance of itself to be created, and usually gives simple access to that instance.

--本质上讲,一个单件类只允许创建其自身的一个实例,并提供一个全局访问点。

Most commonly, singletons don't allow any parameters to be specified when creating the instance - as otherwise a second request for an instance but with a different parameter could be problematic!(If the same instance should be accessed for all requests with the same parameter, the factory pattern is more appropriate.)

--通常情况下,单件类不允许在创建实例的时候指定参数-否则第二次请求时的参数不同与第一次,将无法提供此请求参数所需的实例。(如果可以通过相同的参数来获取一个类的同一实例,那么工厂模式更适合)

 

This article deals only with the situation where no parameters are required.

--本文只讨论不要求传参情况。

Typically a requirement of singletons is that they are created lazily - i.e. that the instance isn't created until it is first needed.

--单件模式的典型情况是延迟创建,也就是知道第一次被请求才创建这个类的实例。

There are various different ways of implementing the singleton pattern in C#.

--这里将介绍在C#中实现单件模式的多种方法。

I shall present them here in reverse order of elegance, starting with the most commonly seen, which is not thread-safe, and working up to a fully lazily-loaded, thread-safe, simple and highly performant version.

--在这里,我将从劣到优的顺序介绍他们,从最常见的,非线程安全的,到晚加载的,线程安全的,简单且高效的版本。

Note that in the code here, I omit the private modifier, as it is the default for class members.

--在本文的代码中需注意,我将忽略类成员的默认Private修饰符。

In many other languages such as Java, there is a different default, and private should be used.

--在一些其他的语言中,比如:java,将会使用其他的默认的私有(Private)修饰符。

 

All these implementations share four common characteristics, however:

--尽管如此,本文遵循如下四个一般约定:

  • A single constructor, which is private and parameterless.
    --一个简单的私有并无参的构造器。
    This prevents other classes from instantiating it (which would be a violation of the pattern).
    --用于预防其他类实例化单件类(如果其他类能建多个实例将违背该模式原则)
    Note that it also prevents subclassing - if a singleton can be subclassed once, it can be subclassed twice, and if each of those subclasses can create an instance, the pattern is violated.
    --注意:同样需要防止子类继承--如果一个单件类能被继承一次,那么就能被继承第二次,并且如果他们的子类能创建一个实例,那么也就违背了模式的初衷.
    The factory pattern can be used if you need a single instance of a base type, but the exact type isn't known until runtime.
    --如果需要一个父类的简单实例,但只有到运行时才能知道实例的确切类型,则可以考虑使用工厂模式。
  • The class is sealed. This is unnecessary, strictly speaking, due to the above point, but may help the JIT to optimise things more.
    --类是终极类.严格来讲,基于上面的观点,这点不是必须的,但有助于JIT确定类的类型。
  • A static variable which holds a reference to the single created instance, if any.
    --如果存在静态变量的话,使用静态遍历存储创建的单件类的简单实例。
  • A public static means of getting the reference to the single created instance, creating one if necessary.
    --一个公开的静态方法,用于返回单件类的简单实例,如果必要将返回一个新建实例。

Note that all of these implementations also use a public static property Instance as the means of accessing the instance.

--注意:这里将全部使用一个公开的静态属性方法作为访问实例的入口。

In all cases, the property could easily be converted to a method, with no impact on thread-safety or performance.

--在所有的例子中,属性能被很早的转换成方法,而不会对线程安全和性能带来影响。

First version - not thread-safe

--第一版:--非线程安全

// Bad code! Do not use!--不好的代码!别使用!public sealed class Singleton{    static Singleton instance=null;    Singleton()    {    }    public static Singleton Instance    {        get        {            if (instance==null)            {                instance = new Singleton();            }            return instance;        }    }}

As hinted at before, the above is not thread-safe.

--作为最先的示例,以上代码示非线程安全的。

Two different threads could both have evaluated the test if (instance==null) and found it to be true, then both create instances, which violates the singleton pattern.

--两个不同的线程可能同时执行这段代码(if(instance==null))然后都返回false,就都创建了实例,这样就违背了单件模式的初衷。

Note that in fact the instance may already have been created before the expression is evaluated, but the memory model doesn't guarantee that the new value of instance will be seen by other threads unless suitable memory barriers have been passed.

--注意,事实上实例可能已经建立在判断语句执行之前,但是在内存中不能保证新的实例值能被其他线程访问,除非线程之间屏障被突破。

Second version - simple thread-safety

--第二版:简单线程安全

public sealed class Singleton{    static Singleton instance=null;    static readonly object padlock = new object();    Singleton()    {    }    public static Singleton Instance    {        get        {            lock (padlock)            {                if (instance==null)                {                    instance = new Singleton();                }                return instance;            }        }    }}

This implementation is thread-safe.

--这个执行是线程安全的。

The thread takes out a lock on a shared object, and then checks whether or not the instance has been created before creating the instance.

--线程对一个共享对象进行加锁,然后在传进实例前检查实例是否被创建。

This takes care of the memory barrier issue (as locking makes sure that all reads occur logically after the lock acquire, and unlocking makes sure that all writes occur logically before the lock release) and ensures that only one thread will create an instance (as only one thread can be in that part of the code at a time - by the time the second thread enters it,the first thread will have created the instance, so the expression will evaluate to false).

--这样就处理了线程隔阂造成的结果(当锁定一个对象时能确保所有的读取操作发生在锁定对象被获取之后,而解锁则能确保所有的写操作在释放锁对象之前)并且确保只有一个线程能创建实例(在同一时间只有一个线程能进入锁定的代码,当第二个进程进入时,前面一个线程已经创建了类实例,所以判断对象是否创建为flase.

Unfortunately, performance suffers as a lock is acquired every time the instance is requested.

--不幸的是,当请求一个实例的时候,总是执行锁操作影响了性能。

Note that instead of locking on typeof(Singleton) as some versions of this implementation do, I lock on the value of a static variable which is private to the class.

--注意到在开始执行的时候锁了一个这个类的私有静态变量。

Locking on objects which other classes can access and lock on (such as the type) risks performance issues and even deadlocks.

--锁一个其他类的对象,存在极大的风险,甚至可能导致死锁。

This is a general style preference of mine - wherever possible, only lock on objects specifically created for the purpose of locking, or which document that they are to be locked on for specific purposes (e.g. for waiting/pulsing a queue).

--这是我偏爱的一般风格。无论什么地方都可以使用,只锁住专门用来被锁的对象,或者哪些被标识为特殊用途下被锁的对象(比如:等待、执行 的队列)

Usually such objects should be private to the class they are used in. This helps to make writing thread-safe applications significantly easier.

--通常情况下,锁对象为类的私有对象。这样有助于更早,更好的实现可写线程安全程序的实现。

Third version - attempted thread-safety using double-check locking

--第三版:尝试是有双重锁定以实现线程安全

// Bad code! Do not use!public sealed class Singleton{    static Singleton instance=null;    static readonly object padlock = new object();    Singleton()    {    }    public static Singleton Instance    {        get        {            if (instance==null)            {                lock (padlock)                {                    if (instance==null)                    {                        instance = new Singleton();                    }                }            }            return instance;        }    }}

This implementation attempts to be thread-safe without the necessity of taking out a lock every time.

这个实现尝试实现线程安全的同时无需时刻执行锁。

Unfortunately, there are four downsides to the pattern:

不幸的是,这对这个模式有如下4个副作用:

  • It doesn't work in Java.
    --他不能在Java下正常实现。
    This may seem an odd thing to comment on, but it's worth knowing if you ever need the singleton pattern in Java, and C# programmers may well also be Java programmers.
    --这看起来像古怪的言论,但是值得我们思考,如果你曾希望在Java下使用单件模式的话,C#程序员应该做得和Java程序员一样好。
    The Java memory model doesn't ensure that the constructor completes before the reference to the new object is assigned to instance.
    --Java的内存模型不能确保构造器执行完成之前被赋予了新的实例对象。
    The Java memory model underwent a reworking for version 1.5, but double-check locking is still broken after this without a volatile variable (as in C#).
    --Java内存模型在1.5版本的时候进行了重构,但双重检测锁仍然没有得到彻底解决。
  • Without any memory barriers, it's broken in the ECMA CLI specification too.
    --在ECMA CLI标准中,同样没有任何的内存界限限制。
    It's possible that under the .NET 2.0 memory model (which is stronger than the ECMA spec) it's safe, but I'd rather not rely on those stronger semantics, especially if there's any doubt as to the safety.
    --可能在.Net2.0的框架下的内存模型(比ECMA标准更严格)这样是安全的,但是我并不十分相信这种强的限制,特别是对其安全性有怀疑的情况下。
    Making the instance variable volatile can make it work, as would explicit memory barrier calls, although in the latter case even experts can't agree exactly which barriers are required.
    --
    I tend to try to avoid situations where experts don't agree what's right and what's wrong!
    --我倾向于尽量避免出现专家有争议的情况。
  • It's easy to get wrong.
    --很容易发生错误。
    The pattern needs to be pretty much exactly as above - any significant changes are likely to impact either performance or correctness.
    --这个模式要求如上所说的十分完美--任何重大的改变都将带来程序正确性和性能的影响。
  • It still doesn't perform as well as the later implementations.
    --对于后面的实现来讲,他仍然不够完美。

Fourth version - not quite as lazy, but thread-safe without using locks
--第4版:--延迟化不好,但在不使用锁的情况下保证线程安全

public sealed class Singleton{    static readonly Singleton instance=new Singleton();    // Explicit static constructor to tell C# compiler    // not to mark type as beforefieldinit    static Singleton()    {    }    Singleton()    {    }    public static Singleton Instance    {        get        {            return instance;        }    }}

As you can see, this is really is extremely simple - but why is it thread-safe and how lazy is it?

正像你看到的,这的确非常的简单,但是为什么他是线程安全,什么时候初始化呢?

Well, static constructors in C# are specified to execute only when an instance of the class is created or a static member is referenced, and to execute only once per AppDomain.

在C#中,静态构造器只在类的实例被创建之后,或者一个静态成员被引用,并且在运用程序域中只执行一次。

Given that this check for the type being newly constructed needs to be executed whatever else happens, it will be faster than adding extra checking as in the previous examples.
对给定的类型进行检测,需要新的构造器无论在什么情况下都要执行,这种实现要比前面一个版本中的增加额外的检查要快的多。

There are a couple of wrinkles, however:

尽管如此,还有有一些问题:

  • It's not as lazy as the other implementations.
    没办法和其他实现一样实现延迟初始化。
    In particular, if you have static members other than Instance, the first reference to those members will involve creating the instance.
    严格来说,如果有实例的其他静态成员,那么第一次引用这些成员将导致创建实例。
    This is corrected in the next implementation.
    这在下一个版本中将被解决。
  • There are complications if one static constructor invokes another which invokes the first again.
    这会产生一个并发症,如果一个静态的构造器引再次触发另一个第一次就已经触发的。
    Look in the .NET specifications (currently section 9.5.3 of partition II) for more details about the exact nature of type initializers - they're unlikely to bite you, but it's worth being aware of the consequences of static constructors which refer to each other in a cycle.
    查看.Net技术说明(第二部分的9.5.3章节)关于类型初始化的更多细节,他们不太可能会产生错误,但值得知道静态构造器带来的结构,将循环调用彼此。
  • The laziness of type initializers is only guaranteed by .NET when the type isn't marked with a special flag calledbeforefieldinit.
    当没有设定一个叫在某字段之前初始化的特殊标识下,类的延迟初始化只能由.Net来决定。
    Unfortunately, the C# compiler (as provided in the .NET 1.1 runtime, at least) marks all types which don't have a static constructor (i.e. a block which looks like a constructor but is marked static) asbeforefieldinit.
    不幸的是,C#编译器(最少是.Net1.1版本下RunTime)导致所有没有静态构造器的类型(也就是 一块看起来像个构造器,但是是静态的)作为在字段前初始化。
    I now have a discussion page with more details about this issue.
    我现在有一份关于这个问题的讨论页。
    Also note that it affects performance, as discussed near the bottom of this article.
    同时也注意到:他影响性能,在这篇文章的讨论的结尾可以看到。

One shortcut you can take with this implementation (and only this one) is to just makeinstance a public static

一种简单快捷是实现方式是使用一个全局的静态只读的变量来生成实例,并删除其他属性。

readonly variable, and get rid of the property entirely. This makes the basic skeleton code absolutely tiny! Many

这使得基础框架代码变得十分的简洁。

 people, however, prefer to have a property in case further action is needed in future, and JIT inlining is likely to make

尽管如此,还是很多人喜欢有将来功能需要的属性,而运行时的编译执行技术也很可能保证运行的性能没有多大差别。

 the performance identical. (Note that the static constructor itself is still required if you require laziness.)

(注意,如果想要延迟加载,静态的构造函数本身就要求存在。)

Fifth version - fully lazy instantiation

第五版-完全言辞加载实例化

public sealed class Singleton{    Singleton()    {    }    public static Singleton Instance    {        get        {            return Nested.instance;        }    }        class Nested    {        // Explicit static constructor to tell C# compiler        // not to mark type as beforefieldinit        static Nested()        {        }        internal static readonly Singleton instance = new Singleton();    }}

Here, instantiation is triggered by the first reference to the static member of the nested class, which only occurs in

在这里,含有静态成员的Nested类将触发实例化。

Instance. This means the implementation is fully lazy, but has all the performance benefits of the previous ones. Note

这就意味着这个实例化过程是完全延迟的,比前一个版本有着更好的性能。

 that although nested classes have access to the enclosing class's private members, the reverse is not true, hence

注意到通过Nested类

 the need for instance to be internal here. That doesn't raise any other problems, though, as the class itself is private. The code is a bit more complicated in order to make the instantiation lazy, however.

Performance vs laziness

立即执行 Vs 延迟

In many cases, you won't actually require full laziness - unless your class initialization does something particularly

在一些情况下,你确实不需要完全的延迟加载,除非你的类初始化工作将十分耗时或者会在其他地方产生副作用,

time-consuming, or has some side-effect elsewhere, it's probably fine to leave out the explicit static constructor shown above. This can increase performance as it allows the JIT compiler to make a single check (for instance at the start of a method) to ensure that the type has been initialized, and then assume it from then on. If your singleton instance is referenced within a relatively tight loop, this can make a (relatively) significant performance difference. You should decide whether or not fully lazy instantiation is required, and document this decision appropriately within the class. (See below for more on performance, however.)

Exceptions

例外

 

Sometimes, you need to do work in a singleton constructor which may throw an exception, but might not be fatal to

有时,你需要用采用单件的构造函数,虽然这个构造函数可能抛出对整个运用系统非致命的异常。

the whole application. Potentially, your application may be able to fix the problem and want to try again. Using type

有可能,你的运用程序可能可以修复这个问题并进行尝试。

initializers to construct the singleton becomes problematic at this stage. Different runtimes handle this case

在这种情况下,用类型初始化来实现单件将成为问题。

differently, but I don't know of any which do the desired thing (running the type initializer again), and even if one did, your code would be broken on other runtimes. To avoid these problems, I'd suggest using the second pattern listed on the page - just use a simple lock, and go through the check each time, building the instance in the method/property if it hasn't already been successfully built.

Thanks to Andriy Tereshchenko for raising this issue.

A word on performance

关于性能

 

A lot of the reason for this page stemmed from people trying to be clever, and thus coming up with the double-

人们试着采用更好的方式,这样随之而来的是双重锁的解决方案。

checked locking algorithm. There is an attitude of locking being expensive which is common and misguided. I've

但这种方法是常用但昂贵和不理智的。

written a very quick benchmark which just acquires singleton instances in a loop a billion ways, trying different variants. It's not terribly scientific, because in real life you may want to know how fast it is if each iteration actually involved a call into a method fetching the singleton, etc. However, it does show an important point. On my laptop, the slowest solution (by a factor of about 5) is the locking one (solution 2). Is that important? Probably not, when you bear in mind that it still managed to acquire the singleton a billion times in under 40 seconds. That means that if you're "only" acquiring the singleton four hundred thousand times per second, the cost of the acquisition is going to be 1% of the performance - so improving it isn't going to do a lot. Now, if youare acquiring the singleton that often - isn't it likely you're using it within a loop? If you care that much about improving the performance a little bit, why not declare a local variable outside the loop, acquire the singleton once andthen loop. Bingo, even the slowest implementation becomes easily adequate.

I would be very interested to see a real world application where the difference between using simple locking and using one of the faster solutions actually made a significant performance difference.

Conclusion (modified slightly on January 7th 2006)

There are various different ways of implementing the singleton pattern in C#.

在C#中,有非常多的方式来实现Singleton模式。
A reader has written to me detailing a way he has encapsulated the synchronization aspect, which while I acknowledge may be useful in a fewvery particular situations (specifically where you want very high performance,and the ability to determine whether or not the singleton has been created,and full laziness regardless of other static members being called). I don't personally see that situation coming up often enough to merit going further with on this page, but pleasemail me if you're in that situation.

My personal preference is for solution 4: the only time I would normally go away from it is if I needed to be able to call other static methods without triggering initialization,

我个人的选择是方案四:只有当我需要能够调用其他的静态方法,而有不想触发初始化值,或者我需要判断单件类是否已经初始化的时候,我才会不采用方案四。

 or if I needed to know whether or not the singleton has already been instantiated. I don't remember the last time I was in that situation, assuming I even have. In that case, I'd probably go for solution 2, which is still nice and easy to get right.

我都记不起来遇到这样的情况是什么时候了,如果有遇到过的话。在那种情况下,我大概会选择方案2,同样可以非常漂亮而又简单的实现。

Solution 5 is elegant, but trickier than 2 or 4, and as I said above, the benefits it provides seem to only be rarely useful.

方案5很好,但比方案2和4来得晦涩,正如我前面所说,它带来效益仅仅是比较少用到的用处(延迟加载)。

(I wouldn't use solution 1 because it's broken, and I wouldn't use solution 3 because it has no benefits over 5.)

(我不使用解决方案1因为他不好,我也不想使用实现3因为他没有方案5好)

原创粉丝点击