258

换句话说,这个 Singleton 实现线程是否安全:

public class Singleton
{
    private static Singleton instance;

    private Singleton() { }

    static Singleton()
    {
        instance = new Singleton();
    }

    public static Singleton Instance
    {
        get { return instance; }
    }
}
4

10 回答 10

199

在创建任何类的实例或访问任何静态成员之前,静态构造函数保证在每个应用程序域中只运行一次。https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-constructors

所示的实现对于初始构造是线程安全的,也就是说,构造 Singleton 对象不需要锁定或空测试。但是,这并不意味着实例的任何使用都会被同步。有多种方法可以做到这一点;我在下面展示了一个。

public class Singleton
{
    private static Singleton instance;
    // Added a static mutex for synchronising use of instance.
    private static System.Threading.Mutex mutex;
    private Singleton() { }
    static Singleton()
    {
        instance = new Singleton();
        mutex = new System.Threading.Mutex();
    }

    public static Singleton Acquire()
    {
        mutex.WaitOne();
        return instance;
    }

    // Each call to Acquire() requires a call to Release()
    public static void Release()
    {
        mutex.ReleaseMutex();
    }
}
于 2008-08-10T08:46:48.413 回答
91

虽然所有这些答案都给出了相同的一般答案,但有一个警告。

请记住,泛型类的所有潜在派生都被编译为单独的类型。因此,在为泛型类型实现静态构造函数时要小心。

class MyObject<T>
{
    static MyObject() 
    {
       //this code will get executed for each T.
    }
}

编辑:

这是演示:

static void Main(string[] args)
{
    var obj = new Foo<object>();
    var obj2 = new Foo<string>();
}

public class Foo<T>
{
    static Foo()
    {
         System.Diagnostics.Debug.WriteLine(String.Format("Hit {0}", typeof(T).ToString()));        
    }
}

在控制台中:

Hit System.Object
Hit System.String
于 2008-12-02T22:46:03.103 回答
30

使用静态构造函数实际上线程安全的。静态构造函数保证只执行一次。

来自 C# 语言规范

类的静态构造函数在给定的应用程序域中最多执行一次。静态构造函数的执行由在应用程序域中发生的以下第一个事件触发:

  • 类的一个实例被创建。
  • 类的任何静态成员都被引用。

所以是的,您可以相信您的单例将被正确实例化。

Zooba 提出了一个很好的观点(也比我早了 15 秒!)静态构造函数不能保证对单例的线程安全共享访问。这将需要以另一种方式处理。

于 2008-08-10T08:46:52.577 回答
9

这是 C# 单例上上述 MSDN 页面的 Cliffnotes 版本:

始终使用以下模式,您不会出错:

public sealed class Singleton
{
   private static readonly Singleton instance = new Singleton();

   private Singleton(){}

   public static Singleton Instance
   {
      get 
      {
         return instance; 
      }
   }
}

除了明显的单例特性之外,它还免费为您提供了这两件事(相对于 C++ 中的单例):

  1. 惰性构造(如果从未调用过,则不构造)
  2. 同步
于 2011-11-23T19:34:05.087 回答
6

静态构造函数保证每个 App Domain 只触发一次,所以你的方法应该没问题。但是,它在功能上与更简洁的内联版本没有什么不同:

private static readonly Singleton instance = new Singleton();

当您懒惰地初始化事物时,线程安全是一个更大的问题。

于 2008-08-10T08:48:33.697 回答
5

静态构造函数将在任何线程被允许访问该类之前完成运行。

    private class InitializerTest
    {
        static private int _x;
        static public string Status()
        {
            return "_x = " + _x;
        }
        static InitializerTest()
        {
            System.Diagnostics.Debug.WriteLine("InitializerTest() starting.");
            _x = 1;
            Thread.Sleep(3000);
            _x = 2;
            System.Diagnostics.Debug.WriteLine("InitializerTest() finished.");
        }
    }

    private void ClassInitializerInThread()
    {
        System.Diagnostics.Debug.WriteLine(Thread.CurrentThread.GetHashCode() + ": ClassInitializerInThread() starting.");
        string status = InitializerTest.Status();
        System.Diagnostics.Debug.WriteLine(Thread.CurrentThread.GetHashCode() + ": ClassInitializerInThread() status = " + status);
    }

    private void classInitializerButton_Click(object sender, EventArgs e)
    {
        new Thread(ClassInitializerInThread).Start();
        new Thread(ClassInitializerInThread).Start();
        new Thread(ClassInitializerInThread).Start();
    }

上面的代码产生了下面的结果。

10: ClassInitializerInThread() starting.
11: ClassInitializerInThread() starting.
12: ClassInitializerInThread() starting.
InitializerTest() starting.
InitializerTest() finished.
11: ClassInitializerInThread() status = _x = 2
The thread 0x2650 has exited with code 0 (0x0).
10: ClassInitializerInThread() status = _x = 2
The thread 0x1f50 has exited with code 0 (0x0).
12: ClassInitializerInThread() status = _x = 2
The thread 0x73c has exited with code 0 (0x0).

即使静态构造函数需要很长时间才能运行,其他线程也会停止并等待。所有线程都读取在静态构造函数底部设置的 _x 的值。

于 2017-01-01T06:37:26.187 回答
3

公共语言基础设施规范保证“类型初始化程序应为任何给定类型只运行一次,除非用户代码显式调用。” (第 9.5.3.1 节。)因此,除非您在松散调用 Singleton::.cctor 上有一些古怪的 IL(不太可能),否则您的静态构造函数将在使用 Singleton 类型之前只运行一次,只会创建一个 Singleton 实例,并且您的 Instance 属性是线程安全的。

请注意,如果 Singleton 的构造函数访问 Instance 属性(甚至是间接访问),则 Instance 属性将为 null。您可以做的最好的事情是检测何时发生这种情况并引发异常,方法是检查属性访问器中的实例是否为非空。静态构造函数完成后,Instance 属性将非空。

正如Zoomba 的回答指出的那样,您需要使 Singleton 可以安全地从多个线程访问,或者围绕使用单例实例实施锁定机制。

于 2008-08-10T08:45:57.977 回答
2

只是为了迂腐,但没有静态构造函数之类的东西,而是静态类型初始化器,这里有一个循环静态构造函数依赖的小演示,说明了这一点。

于 2010-03-02T12:37:43.320 回答
2

尽管其他答案大多是正确的,但静态构造函数还有另一个警告。

根据ECMA-335 公共语言基础设施 的第II.10.5.3.3 节的竞争和死锁

除非从类型初始化程序(直接或间接)调用的某些代码显式调用阻塞操作,否则单独的类型初始化不应造成死锁。

以下代码导致死锁

using System.Threading;
class MyClass
{
    static void Main() { /* Won’t run... the static constructor deadlocks */  }

    static MyClass()
    {
        Thread thread = new Thread(arg => { });
        thread.Start();
        thread.Join();
    }
}

原作者是 Igor Ostrovsky,请在此处查看他的帖子。

于 2014-03-25T12:19:27.570 回答
1

静态构造函数保证是线程安全的。另外,请查看 DeveloperZen 上关于 Singleton 的讨论:http://web.archive.org/web/20160404231134/http: //www.developerzen.com/2007/07/15/whats-wrong-with-this-code -1-讨论/

于 2008-08-10T23:38:26.520 回答