c#静态构造函数线程安全吗?

换句话说,这个单例实现是线程安全的:

public class Singleton
{
private static Singleton instance;


private Singleton() { }


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


public static Singleton Instance
{
get { return instance; }
}
}
67553 次浏览

公共语言基础架构规范保证“除非用户代码显式调用,否则对于任何给定类型,类型初始化器只能运行一次。”(部分9.5.3.1。)所以除非你有一些古怪的IL在调用Singleton::。在使用Singleton类型之前,你的静态构造函数只会运行一次,只会创建一个Singleton实例,并且你的instance属性是线程安全的。

注意,如果Singleton的构造函数访问Instance属性(即使是间接的),那么Instance属性将为空。您能做的最好的事情是检测何时发生这种情况,并通过检查属性访问器中的实例是非空来抛出异常。在静态构造函数完成后,Instance属性将是非空的。

正如Zoomba的回答指出的那样,你需要让单例在多线程中可以安全访问,或者在使用单例实例时实现一个锁定机制。

在创建类的任何实例或访问任何静态成员之前,每个应用程序域保证只运行一次静态构造函数。https://learn.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();
}
}

使用静态构造函数实际上是 threadsafe。静态构造函数保证只执行一次。

来自c#语言规范:

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

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

所以,是的,你可以相信你的单例会被正确地实例化。

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

静态构造函数保证每个应用程序域只触发一次,所以你的方法应该是OK的。然而,它在功能上与更简洁的内联版本没有什么不同:

private static readonly Singleton instance = new Singleton();

当您在惰性初始化事物时,线程安全更是个问题。

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

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

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

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

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

下面是上面MSDN页面上c#单例的Cliffnotes版本:

使用下面的模式,总是,你不会错:

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


private Singleton(){}


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

除了明显的单例特性之外,它还免费提供了以下两个功能(相对于c++中的单例而言):

  1. 惰性构造(如果从未调用,则不进行构造)
  2. 同步

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

    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集合的值。

静态构造函数被锁定。当类型初始化式正在运行时,任何其他试图以触发类型初始化式的方式访问该类的线程都将阻塞。

但是,运行类型初始化式的线程可以访问未初始化的静态成员。因此,如果从UI线程运行,请确保不要从类型初始化器调用Monitor.Enter() (lock(){})或ManualResetEventSlim.Wait()——它们是“interruptible”等待,导致事件循环运行,在类型初始化器仍未完成时执行程序的任意其他部分。

最好使用托管块而不是非托管块。WaitHandle.WaitOneWaitHandle.WaitAny0, WaitHandle.WaitAny1, WaitHandle.WaitAny2, WaitHandle.WaitAny3, WaitHandle.WaitAny4, WaitHandle.WaitAny5等等都对WaitHandle.WaitAny6和WaitHandle.WaitAny7有响应。同样,如果你的线程是在一个单线程的公寓,所有这些托管阻塞操作将正确泵消息在你的公寓当你的线程被阻塞: