这个页面存在的很多原因是人们试图变得聪明,因此提出了双重检查锁定算法。我们常常认为锁定是昂贵的,这被误导的。我写了一个非常快速的基准测试,在一个循环中获取10亿次单例实例,并尝试不同的变体。这并不是很科学,因为在现实生活中,您可能想知道如果每次迭代都涉及到对获取单例的方法的调用,那么速度有多快。然而这确实显示了一个重要的观点。在我的笔记本电脑上,最慢的解决方案(大约5倍)是锁定解决方案(解决方案2)。这很重要吗?可能不会,当您记住它仍然能够在40秒内获取10亿次Singleton。(注意:这篇文章最初是在很久以前写的——现在我希望有更好的性能。)这意味着,如果你是“仅仅”每秒获得40万次单例实例,那么花费的成本将是1%的性能——所以不会做很多事情去改进它。现在,如果你经常 获得单例实例——你是否可能在循环中使用它?如果您非常关心如何提高性能,为什么不在循环外声明一个局部变量,先获取一次Singleton,然后再循环呢。Bingo,即使是最慢的实现性能也足够了。
我非常有兴趣看到一个真实的应用程序,在这个应用程序中,使用简单锁定和使用一种更快的解决方案之间的差异实际上会带来显著的性能差异。
异常有时,您需要在单例构造函数中执行一些操作,这可能会抛出异常,但可能不会对整个应用程序造成致命影响。您的应用程序可能能够解决此问题,并希望再次尝试。在这个阶段,使用类型初始化器来构造单例会出现问题。不同的运行时处理这种情况的方式不同,但我不知道有哪些运行时执行了所需的操作(再次运行类型初始化程序),即使有一个运行时这样做,您的代码也会在其他运行时被破坏。为了避免这些问题,我建议使用文章里列出的第二种模式 ——只需使用一个简单的锁,并每次都进行检查,如果尚未成功构建实例,则在方法/属性中构建实例。
结论在C#中实现单例模式有各种不同的方法。读者已经写信给我详细说明了他已经封装了同步方面的方法,虽然我承认这可能在一些非常特殊的情况下有用(特别是在你想要非常高性能的地方,以及确定单例是否已经创建,并完全懒惰,而不考虑其他静态成员被调用)。我个人并不认为这种情况会经常出现,值得在这篇文章中进一步改进,但如果你处于这种情况,请发邮件给我。
我的个人的偏好是解决方案4:我通常唯一一次不采用它是因为我需要能够在不触发初始化的情况下调用其他静态方法,或者如果我需要知道单例是否已经被实例化。我不记得上次我处于那种情况是什么时候了,假设我有过,在那种情况下,我可能会选择解决方案2,这仍然是很好的,很容易正确实现。
解决方案5很优雅,但是比2或4更复杂,正如我上面所说,它提供的好处似乎只是很少有用。解决方案6是一种更简单的方法来实现懒惰,如果你使用.NET 4.它还有一个优势,它显然是懒惰的。我目前仍然倾向于使用解决方案4,这仅仅是出于习惯——但如果我与没有经验的开发人员合作,我很可能会选择解决方案6作为一种简单且普遍适用的模式。
(我不会使用解决方案1,因为它是有缺陷的,我也不会使用解决方案3,因为它的好处没有超过5。)