这是避免双重检查锁的有效且优化的方法:
public class SomeBaseClass
{
protected static object InitializeLock = new object();
protected static bool IsInitialized = false;
public void SomeFunction()
{
if (!IsInitialized)
{
System.Threading.Thread.MemoryBarrier();
lock (InitializeLock)
{
// do init stuff
IsInitialized = true;
}
}
//Do stuff that have to happen when function is called
}
}
这是双重检查的选择:
public class SomeBaseClass
{
protected static object InitializeLock = new object();
protected static bool IsInitialized = false;
public void SomeFunction()
{
if (!IsInitialized)
{
lock (InitializeLock)
{
if (!IsInitialized)
{
// do init stuff
IsInitialized = true;
}
}
}
//Do stuff that have to happen when function is called
}
}
答案 0 :(得分:6)
不,因为线程切换可能在两个线程通过if (!IsInitialized)
有一篇很棒的文章,在创建单身人士的背景下解释了这个主题:http://csharpindepth.com/Articles/General/Singleton.aspx(作者Jon Skeet)
答案 1 :(得分:5)
这是今天第二次出现这个问题。参见:
对你的问题的简短回答是否定的,这绝对无效。如果针对正在初始化的任何状态的非易失性读取重新排序“IsInitialized”的非易失性读取,那么代码路径上的任何类型的都不会有内存屏障,因此读取可以重新排序,因此“IsInitialized”可以为真,而过时的缓存未初始化状态仍然良好。
你需要做的是(1)不要进行双重检查锁定;这是危险的,或(2)确保始终至少有一个易读的IsInitialized,以防止读取初始化状态及时向后移动。
答案 2 :(得分:4)
第一个示例中的MemoryBarrier
调用完全是多余的,因为后续的lock
调用无论如何都会创建隐式内存屏障。
即使您在第一次IsInitialized
检查之前移动了内存屏障,代码仍然不安全:在IsInitialized
检查和lock
之间有一个窗口可以中断线程声明。这就是为什么您通常需要在IsInitialized
块内进行第二次lock
检查。
答案 3 :(得分:0)
您可以通过IsInitialized
标记volatile
来帮助检查,这会阻止其他线程缓存它(自锁定以来这是一个非常小的改进),但是你锁定后仍然需要旗帜。换句话说,除非你使用一些棘手的初始化,否则你无法避免双重检查锁。
然而,如果你重新设计你的课程,你可以取消锁定,如果你采取乐观的方法改变状态......这应该像魅力一样:
public class Internals
{
private readonly bool IsInitialized;
public Internals(bool initialized)
{
IsInitialized = initialized;
}
}
public class SomeBaseClass
{
protected static Internals internals = new Internals(false);
public void SomeFunction()
{
do
{
Internals previous = internals;
}while(!previous.IsInitialized && previous != Interlocked.CompareExchange(internals, new Internals(true), previous))
}
}