C# 单例模式的线程安全性

3
我阅读了Jon Skeet关于如何实现C#单例模式的权威文章,并遵循以下模式。请注意,我的构造函数可能会执行一些工作,例如创建和填充字符串数组(或者它可能创建一些对象并将它们分配给私有变量等):
public class MyClass
    {                
        /// <summary>
        /// Get singleton instance of this class.
        /// </summary>
        public static readonly MyClass Instance = new MyClass();


        /// <summary>
        /// a collection of strings.
        /// </summary>
        private string[] strings;        

        private MyClass()
        {            
            this.strings = new string[]
            {
                "a",
                "b",
                "c",
                "d",
                "e" 
            };
        }

        public void MyMethod()
        {
           // tries to use this.strings.
           // can a null ref exception happen here when running multithreaded code?
        }
}

上面的代码是线程安全的吗?我问这个问题是因为我在一个asp.net应用服务器上运行类似的代码,日志中出现了空引用异常(不确定空引用是否与上述代码有关 - 我认为不是 - 日志中的调用堆栈并不有用)。


是的,初始化是线程安全的。它保证只会发生一次,并且只会实例化您的类的一个实例。 - Cameron
是的,它是线程安全的。额外的信息 - 如果您想要“每个线程”单例,可以使用ThreadStatic属性标记静态字段。 - gp.
2个回答

1
我真的看不出为什么它不应该是线程安全的。特别是考虑到Jon的第四个线程安全版本本质上是相同的。
我唯一看到的问题是你没有静态构造函数。(这可能会导致问题,请参见this)如果你添加静态构造函数(即使它是空的),你将拥有Jon Skeet所谓的线程安全。
public class MyClass
{
    public static readonly MyClass Instance = new MyClass();

    // Explicit static constructor to tell C# compiler
    // not to mark type as beforefieldinit
    static MyClass() { }
}

1
根据Jon Skeet的文章,添加静态构造函数将使此实现线程安全:

只有在类型未标记为beforefieldinit时,.NET才保证类型初始化器的延迟性。不幸的是,C#编译器(至少在.NET 1.1运行时中提供)将所有没有静态构造函数(即看起来像构造函数但被标记为静态的块)的类型标记为beforefieldinit。

(参见http://csharpindepth.com/articles/general/singleton.aspx#cctor
如现在所示,它不是线程安全的。如果您像这样做,它就变成了线程安全的:
public class MyClass
{
    /// <summary>
    /// Get singleton instance of this class
    /// </summary>
    public static readonly MyClass Instance = new MyClass();

    static MyClass()
    {
        //causes the compiler to not mark this as beforefieldinit, giving this thread safety
        //for accessing the singleton.
    }

    //.. the rest of your stuff..
}

我认为beforefieldinit只控制类型初始化器的惰性,不应影响线程安全性? - morpheus
@Morpheus:没错。原始示例是线程安全的,但可能不会进行延迟初始化。就我个人而言,如果懒惰初始化对我很重要,我会使用Lazy<T>类来实现单例模式(我认为Skeet的文章早于该类,除非他在最初撰写后进行了更新)。 - Peter Duniho
懒加载对我来说并不重要。那么,除此之外,问题中发布的代码有什么问题吗? - morpheus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接