我在想,如果采用以下方式是否更有效率,因为这样可以避免不断的线程锁定,或者有隐藏的问题吗?
你的问题是“通过采取危险的低锁定模式是否会获得性能提升?” 这是完全错误的问题。永远不要这样推理!那样会浪费时间、精力,并且会产生疯狂、难以调试的 bug。
正确的问题是“我的测量结果是否强烈表明我本来就存在性能问题?” 如果答案是“否”,那么你就完成了。
只有当答案是“是”时,你才应该问下一个问题,即“是否可以通过消除锁上的争用来消除性能问题?”
如果答案是“是”,那么消除锁上的争用并回到第一个问题。
只有当答案是“否”时,你才应该问下一个问题,即“采用低锁定解决方案是否能够提供可接受的性能?”
请注意,为了回答这个问题,你必须处于这样一种情况:无争用锁所带来的十纳秒惩罚是你性能的关键因素。很少有人处于十或二十纳秒太长的位置。
在极其不可能的情况下,如果答案是“是”,那么你应该继续下一个问题,即“正确实现的双重检查锁定是否消除了我的性能问题?”
如果双重检查锁定不够快,那么实现它就毫无意义。你必须用其他方法解决问题。
只有当答案是“是”时,你才应该实现双重检查锁定。
现在让我们来看看你实际的问题:
这种实现方式是正确的。然而,一旦你偏离了正常模式,所有的保证都将不存在。例如:
static object sync = new object();
static bool b = false;
static int x = 0;
static int GetIt()
{
if (!b)
{
lock(sync)
{
if (!b)
{
b = true;
x = ExpensiveComputation();
}
}
}
return x;
}
看起来没问题,对吗?但实际上不对!要考虑低锁定路径。由于这条路径上没有屏障,因此一个线程可以预取x值为零,然后另一个线程可以运行并将b设置为true和x设置为123,然后原始线程可以获取b,得到true,并返回预取的x。
那么有什么解决方案呢?按我的偏好顺序,它们是:
- 不要懒惰。仅初始化静态字段一次即可。
- 使用Jon Skeet网站上记录的受认可的延迟单例模式。
- 使用
Lazy<T>
。
- 使用单次检查锁定。
- 如果您不关心单例是否会在罕见情况下创建两次并且其中一个被丢弃,请使用
InterlockedCompareExchange
。
- 使用经过认可的双重检查锁定模式。
static Foo _instance;
那么它不是线程安全的,因为你没有正确的可见性保证。必须使用 volatile 或者自己引入正确的内存屏障。 - Voo