如果我有一个深度不可变的类型(所有成员都是只读的,如果它们是引用类型成员,那么它们也引用深度不可变的对象)。
我想在类型上实现一个延迟初始化的属性,如下所示:
private ReadOnlyCollection<SomeImmutableType> m_PropName = null;
public ReadOnlyCollection<SomeImmutableType> PropName
{
get
{
if(null == m_PropName)
{
ReadOnlyCollection<SomeImmutableType> temp = /* do lazy init */;
m_PropName = temp;
}
return m_PropName;
}
}
据我所知:
m_PropName = temp;
...是线程安全的。我不太担心两个线程同时竞争初始化,因为这种情况很少见,从逻辑角度来看,两个结果都是相同的,如果没有锁,我宁愿不使用锁到。
这行得通吗?优缺点都有什么?
Edit:感谢您的回答。我可能会继续使用锁。然而,令我惊讶的是没有人提出编译器意识到 temp 变量是不必要的,并且直接分配给 m_PropName 的可能性。如果是这种情况,那么读取线程可能会读取尚未完成构造的对象。编译器会阻止这种情况吗?
(答案似乎表明运行时不允许这种情况发生。)
Edit:因此,我决定采用 Interlocked CompareExchange 方法,其灵感来自乔·达菲的这篇文章 https://web.archive.org/web/20080216100813/http://www.bluebytesoftware.com/blog/2007/11/17/ImmutableTypesCanCopyTheWorldSafely.aspx.
基本上:
private ReadOnlyCollection<SomeImmutableType> m_PropName = null;
public ReadOnlyCollection<SomeImmutableType> PropName
{
get
{
if(null == m_PropName)
{
ReadOnlyCollection<SomeImmutableType> temp = /* do lazy init */;
System.Threading.Interlocked(ref m_PropName, temp, null);
}
return m_PropName;
}
}
这应该确保在此对象实例上调用此方法的所有线程都将获得对同一对象的引用,因此 == 运算符将起作用。有可能会浪费工作,这很好 - 它只是使这是一个乐观的算法。
正如下面的一些评论所述,这取决于 .NET 2.0 内存模型的工作。否则,m_PropName 应该被声明为易失性的。