Jon Skeet在http://csharpindepth.com/Articles/General/Singleton.aspx上发表的精彩文章以及我读过的其他文章清楚地表明,双重检查锁定在C#和Java中都不起作用,除非有人明确将该实例标记为“volatile”。如果不这样做,则将其与null进行比较的检查可能会返回false,即使实例构造函数尚未完成运行。在Skeet先生的第三个样本中,他清楚地说明了这一点:“Java内存模型不能确保构造函数在将新对象的引用分配给实例之前完成.Java内存模型经历了1.5版的重做,但是双重在此之后,如果没有volatile变量(如在C#中那样),那么-check锁定仍会被破坏“
然而,大多数人都同意(包括Skeet先生,他的文章中的第4和第5个样本),使用静态初始化是获取线程安全单例实例的简单方法。他声明“C#中的静态构造函数被指定仅在创建类的实例或引用静态成员时执行,并且每个AppDomain只执行一次。”
这是有道理的,但似乎缺少的是保证仅在构造函数完成后才分配对新对象的引用 - 否则我们会遇到同样的问题,使得双重检查锁定失败,除非你将实例标记为volatile。是否有保证,当使用静态初始化来调用实例构造函数(而不是从属性的get {}调用实例构造函数时,就像我们使用双重检查锁定一样),构造函数将在任何其他线程之前完全完成可以获得对象的引用吗?
谢谢!
答案 0 :(得分:9)
构造函数是否会在任何其他线程获得对象的引用之前完全完成?
静态初始化程序将仅按每个AppDomain
(至少由系统)调用一次,并以同步方式调用“beforefieldinit”。因此,假设您没有做任何奇怪的事情,静态初始化程序中分配的任何静态字段应该没问题;任何其他使用静态字段的尝试都应该在静态构造函数后面保持(阻塞)。
仅在构造函数完成后才分配对新对象的引用
它发生时就会发生。例如,任何静态字段初始化程序都会在之前发生您通常认为的构造函数。但由于其他线程被阻止,这应该不是问题。
然而:
答案 1 :(得分:2)
是;保证在声明中,每个AppDomain只执行一次。
如果它可以执行多次,那只会是不安全的;如上所述,它不能,所以一切都很好:))