如果您正在多线程,那么答案是肯定的。
小记:
显然使用情况下,你不能把一个锁,因为它是空:-)
的锁定模式是正常(这就是所谓的Double-checked locking):
if (instance == null)
{
lock (something)
{
if (instance == null)
{
instance = new Class();
}
}
}
如果你想(如果创建类不贵),你可以做:
if (instance == null)
{
Interlocked.CompareExchange(ref instance, new Class(), null);
// From here you are sure the instance field containts a "Class"
}
这段代码的唯一问题是两个线程可能会创建一个新的Class(),但只有一个能够使用新Class()的引用来设置实例,所以另一个线程会创建一个无用的Class对象那将是GC。如果创建Class对象是便宜的(例如创建一个List<T>
)就没关系。如果创建Class对象的代价很高(可能是因为构造函数调用一个数据库并做了一个大的查询),那么这个方法是一个禁忌。小记:格林奇已经到了,它已经宣布所有这些多线程都不足够“防万无一失”。他是对的。他错了。这就像是Schrödinger的猫! :-)上次我写了一个多线程程序时,我只是读了所有关于互联网的文献。而且我仍然犯了错误(但是,好吧,我正在尝试编写无锁的MT代码......这真是太重了)。因此,如果您使用的是.NET 4.0,那么使用Lazy<T>
,如果不行的话...拿单声道源文件找到Lazy定义的位置并复制它:-)(许可证非常宽容,但是读取它!) 但是如果什么你需要的是一个单身人士,你可以使用static Lazy<T>
或者如果你没有.NET 4.0,你可以使用http://www.yoda.arachsys.com/csharp/singleton.html(第五个或第四个)的样本。作者建议第四个。请注意,在那里对它的懒惰有一些有趣的警告,但它们写在http://www.yoda.arachsys.com/csharp/beforefieldinit.html)。请注意,你必须“写作”。千万不要想到它会偏离它所写的内容。别。正如你可以看到通过评论阅读,线程是一个硬性的论据。你可能是对的,同时你仍然可能是错的。它就像挥发性物质(挥发性物质?是双关语吗?)化合物......非常非常有趣,非常非常危险。
另一个小提示:在.NET 1.1下它确实很粘。除非你完全知道你在做什么,否则你不应该在1.1中的线程之间共享变量。在2.0版中,他们改变了内存模型(编译器如何优化对内存的访问),并且他们创建了一个“更安全”的内存模型。 .NET 1.1和更早版本的Java(包括1.4版本)都是坑陷阱。
要回答你的问题,一个简单的技巧:当你在想“MT可以打破我的代码吗?”这样做:想象一下Windows(操作系统)是一个懒惰的食人魔。有时候他让一个线程停下半个小时,同时让其他线程运行(技术上他可以做到这一点),而不是30分钟(但没有关于多长时间的真正规则),但是在毫秒内它可以并且如果处理器有一些工作被重载,并且有很多线程固定在特定的处理器上(固定意味着他们告诉操作系统他们只想在某些特定的处理器上运行,所以如果1000个线程固定在处理器1上,而处理器2只能执行1线程没有固定,很明显,处理器2上的线程将会更快!))。所以想象一下,两个线程同时输入你的代码段,同时执行第一行(它们在两个不同的处理器上,它们可以并行地执行),但是两个线程中的一个停止并且具有等待30分钟。同时会发生什么?并且请注意,经常在代码中间停止代码! a = a + 1
是两条指令!它是var temp = a + 1; a = temp;
如果你将这个技巧应用到示例代码中,很容易看出:两个线程都执行if (instance==null)
并传递,然后一个线程停止30分钟,另一个线程初始化该对象,第一个线程恢复并初始化目的。两个对象初始化。没有良好的:-)
我会用一个简单的例子解释了.NET 1.1的问题:
class MyClass
{
public bool Initialized;
public MyClass()
{
Initialized = true;
}
}
MyClass instance = null;
public MyClass GetInstance()
{
if (instance == null)
{
lock (something)
{
if (instance == null)
{
instance = new Class();
}
}
}
return instance;
}
现在...这是代码之前。该问题发生在instance = new Class()
行。让我们分开它的各个部分:
- Class对象的空间由.NET分配。这个空间的引用被保存在某个地方。
- Class的构造函数被调用。 Initialized = true(该字段称为Initialized!)。
- 实例变量被设置为我们之前保存的引用。
在.NET 2.0的较新“强大”内存模型中,这些操作将按此顺序进行。但让我们看看.NET 1.1会发生什么:写入可以重新排序!
- Class对象的空间由.NET分配。这个空间的引用被保存在某个地方。
- 实例变量被设置为我们之前保存的引用。
- Class的构造函数被调用。 Initialized = true(该字段称为Initialized!)。
现在让我们想象一下线程执行,这是由OS 30分钟暂停2点后点3之前另一个线程可以访问实例变量(请注意,在代码中的第一个,如果没有被保护通过一个锁,所以它可以访问它而不用等待第一个线程结束它的工作)并使用它。但类没有真正初始化:他的构造函数没有运行! Boooom!如果您在实例声明(如此volatile MyClass instance = null;
)上使用了volatile
关键字,则不会发生这种情况,因为编译器无法重新排序写入超出易失性字段上的写入。所以他不能在点3之后重新排序点2,因为在点3中它正在写入一个易失性字段。但正如我写的,这是.NET 1.1的一个问题。
现在。如果您想了解线程,请阅读以下内容:http://www.albahari.com/threading/如果您想知道“幕后”会发生什么,您可以阅读http://msdn.microsoft.com/en-us/magazine/cc163715.aspx,但它很沉重。
可能值得[实施辛格尔顿Pattern in C#](http://csharpindepth.com/Articles/General/Singleton.aspx) –