多篇文章说,在.NET中实施双重检查锁定时,您要锁定的字段应应用volatile修饰符。但是为什么呢?考虑以下示例:
public sealed class Singleton { private static volatile Singleton instance; private static object syncRoot = new Object(); private Singleton() {} public static Singleton Instance { get { if (instance == null) { lock (syncRoot) { if (instance == null) instance = new Singleton(); } } return instance; } } }
为什么“锁(syncRoot)”无法实现必要的内存一致性?在“锁定”语句之后读取和写入都是不稳定的,这样就可以实现必要的一致性,这不是真的吗?
挥发是不必要的。好吧,**
volatile用于在变量的读写之间创建内存屏障。 lock使用时,lock除了将对块的访问限制在一个线程之外,还会在内的块周围创建内存屏障。 内存屏障使之成为可能,因此每个线程都读取变量的最新值(而不是某些寄存器中缓存的本地值),并且编译器不会对语句进行重新排序。volatile不需要使用*,因为您已经锁定了。
volatile
lock
约瑟夫·阿尔巴哈里(Joseph Albahari)以前所未有的方式解释了这种东西。
并且一定要查看Jon Skeet的C#实现单例指南。
update : * volatile导致对变量的读取为VolatileReads ,对变量的写入为VolatileWrites,这在x86和CLR上的x64上使用来实现MemoryBarrier。在其他系统上,它们可能更细粒度。
VolatileRead
VolatileWrite
MemoryBarrier
**仅当您在x86和x64处理器上使用CLR时,我的回答才是正确的。在其他内存模型中,例如在Mono(和其他实现),Itanium64和将来的硬件上, 可能 是正确的。这就是乔恩在“陷阱”中的文章中针对双重检查锁定所指的内容。
为了使代码在内存模型较弱的情况下正常工作,可能需要执行以下操作之一(将变量标记为volatile,使用进行读取Thread.VolatileRead或插入对的调用)Thread.MemoryBarrier。
Thread.VolatileRead
Thread.MemoryBarrier
据我了解,在CLR(即使是在IA64上),写入也不会重新排序(写入始终具有发布语义)。但是,在IA64上,除非将其标记为易失性,否则可能会将读取重新排序为先于写入。不幸的是,我无权使用IA64硬件,因此我所说的只是猜测。
我还发现这些文章有所帮助: http://www.codeproject.com/KB/tips/MemoryBarrier.aspx 万斯莫里森的文章(一切链接到这一点,它谈论的双重检查锁定) 克里斯brumme的文章 (所有链接到本) 乔·达菲(Joe Duffy):双重检查锁定的残破变体
luis abreu的有关多线程的系列也很好地概述了这些概念 http://msmvps.com/blogs/luisabreu/archive/2009/06/29/multithreading-load-and- store-reordering.aspx http:// msmvps。 com / blogs / luisabreu / archive / 2009/07/03 / multithreading-introducing-memory- fences.aspx