题
换句话说,这个 Singleton 实现线程安全吗:
public class Singleton
{
private static Singleton instance;
private Singleton() { }
static Singleton()
{
instance = new Singleton();
}
public static Singleton Instance
{
get { return instance; }
}
}
解决方案
在创建类的任何实例或访问任何静态成员之前,保证静态构造函数在每个应用程序域中仅运行一次。 http://msdn.microsoft.com/en-us/library/aa645612.aspx
所示的实现对于初始构造来说是线程安全的,也就是说,构造 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();
}
}
其他提示
虽然所有这些答案都给出了相同的一般答案,但有一个警告。
请记住,泛型类的所有潜在派生都被编译为单独的类型。因此,在为泛型类型实现静态构造函数时要小心。
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
实际上使用静态构造函数 是 线程安全。静态构造函数保证只执行一次。
来自C#语言规范 http://msdn.microsoft.com/en-us/library/aa645612(VS.71).aspx:
类的静态构造函数在给定的应用程序域中最多执行一次。静态构造函数的执行由应用程序域中发生的以下第一个事件触发:
- 创建该类的一个实例。
- 类的任何静态成员都会被引用。
所以是的,您可以相信您的单例将被正确实例化。
Zooba 提出了一个很好的观点(也在我之前 15 秒!):静态构造函数不会保证对单例的线程安全共享访问。这需要以另一种方式处理。
这是上面关于 c# 单例的 MSDN 页面的 Cliffnotes 版本:
始终使用以下模式,不会出错:
public sealed class Singleton
{
private static readonly Singleton instance = new Singleton();
private Singleton(){}
public static Singleton Instance
{
get
{
return instance;
}
}
}
除了明显的单例功能之外,它还免费为您提供以下两件事(相对于 C++ 中的单例):
- 惰性构造(或者如果从未调用过则不构造)
- 同步
静态构造函数保证每个应用程序域仅触发一次,因此您的方法应该没问题。然而,它在功能上与更简洁的内联版本没有什么不同:
private static readonly Singleton instance = new Singleton();
当您延迟初始化事物时,线程安全就更成为一个问题。
这 公共语言基础设施规范 保证“除非用用户代码明确调用,否则任何给定类型的类型初始器应精确运行一次。” (第9.5.3.1节。)因此,除非您在松散的呼叫singleton上有一些whacky IL ::。cctor直接(不太可能)您的静态构造函数将在使用单顿类型之前精确地运行一次,否则只能创建一个Singleton实例,您的实例属性是线程安全。
请注意,如果 Singleton 的构造函数访问 Instance 属性(即使是间接访问),则 Instance 属性将为 null。您能做的最好的事情就是通过检查属性访问器中的实例是否为非空来检测何时发生这种情况并引发异常。静态构造函数完成后,Instance 属性将为非空。
作为 佐姆巴的回答 指出您需要使单例能够安全地从多个线程访问,或者使用单例实例实现锁定机制。
静态构造函数将 结束 跑步 前 任何线程都可以访问该类。
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 值。
只是迂腐一点,但不存在静态构造函数,而是静态类型初始值设定项, 这是一个小 循环静态构造函数依赖的演示说明了这一点。
静态构造函数保证线程安全。另外,请查看 DeveloperZen 上关于 Singleton 的讨论:http://www.developerzen.com/2007/07/15/whats-wrong-with-this-code-1-discussion/
尽管其他答案大多是正确的,但静态构造函数还有另一个警告。
根据章节 II.10.5.3.3 竞争和死锁 的 ECMA-335通用语言基础设施
除非某些代码从类型的初始化器(直接或间接)明确调用阻止操作,否则单独类型初始化不会产生僵局。
下面的代码会导致死锁
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();
}
}
原作者是伊戈尔·奥斯特洛夫斯基,请参阅他的帖子 这里.